Set story hidden state from stories view controller context menu
* Set hidden state from stories view controller context menu * fix toasts when shown on controllers with UIScrollViews as their root views * delete when hiding the onboarding story * run translations script * respond to pr feedback * add hidden state to systemStoryManager * use hidden state from SystemStoryManager for onboarding story * remove unused method
This commit is contained in:
parent
9158e44d66
commit
87d9e5df2a
@ -773,6 +773,7 @@
|
||||
661396AF28BE881E00E0C4DF /* ChainedPromiseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661396AE28BE881E00E0C4DF /* ChainedPromiseTest.swift */; };
|
||||
668CAB3E289983520085A2C3 /* AudioMessagePlaybackRateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668CAB3D289983520085A2C3 /* AudioMessagePlaybackRateView.swift */; };
|
||||
668FE09B28B923A4008B9071 /* Bool+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668FE09A28B923A4008B9071 /* Bool+SSK.swift */; };
|
||||
668FE09F28B947ED008B9071 /* StoryHidingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668FE09E28B947ED008B9071 /* StoryHidingManager.swift */; };
|
||||
669E8FE828B4153C00043D28 /* OWSURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FE728B4153B00043D28 /* OWSURLSession.swift */; };
|
||||
669E8FE928B415C000043D28 /* OWSURLBuilderUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FDB28B02CC400043D28 /* OWSURLBuilderUtil.swift */; };
|
||||
669E8FED28B4177900043D28 /* OWSSignalServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FEC28B4177800043D28 /* OWSSignalServiceMock.swift */; };
|
||||
@ -3026,6 +3027,7 @@
|
||||
668AB0CB28AD610600B31984 /* StoryAuthorUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryAuthorUtil.swift; sourceTree = "<group>"; };
|
||||
668CAB3D289983520085A2C3 /* AudioMessagePlaybackRateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMessagePlaybackRateView.swift; sourceTree = "<group>"; };
|
||||
668FE09A28B923A4008B9071 /* Bool+SSK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bool+SSK.swift"; sourceTree = "<group>"; };
|
||||
668FE09E28B947ED008B9071 /* StoryHidingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryHidingManager.swift; sourceTree = "<group>"; };
|
||||
669E8FDB28B02CC400043D28 /* OWSURLBuilderUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSURLBuilderUtil.swift; sourceTree = "<group>"; };
|
||||
669E8FE528B4149200043D28 /* OWSURLSessionMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSURLSessionMock.swift; sourceTree = "<group>"; };
|
||||
669E8FE728B4153B00043D28 /* OWSURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSURLSession.swift; sourceTree = "<group>"; };
|
||||
@ -6051,6 +6053,7 @@
|
||||
children = (
|
||||
8864072F27F21AA7009916B6 /* Group Reply Sheet */,
|
||||
88423A51280A171E007D2918 /* StoryDirectReplySheet.swift */,
|
||||
668FE09E28B947ED008B9071 /* StoryHidingManager.swift */,
|
||||
88B00D4E28A33B5800BC9CA0 /* StoryPrivateViewsSheet.swift */,
|
||||
8864073027F21AD7009916B6 /* StoryReplyInputToolbar.swift */,
|
||||
88423A53280A2675007D2918 /* StoryReplyPreviewView.swift */,
|
||||
@ -10367,6 +10370,7 @@
|
||||
8864072827EEA658009916B6 /* StoryGroupReplySheet.swift in Sources */,
|
||||
88B00D4B28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift in Sources */,
|
||||
8864072C27F0DA38009916B6 /* StoryGroupReplyViewItem.swift in Sources */,
|
||||
668FE09F28B947ED008B9071 /* StoryHidingManager.swift in Sources */,
|
||||
88863A52280CAE6A00977F69 /* StoryInteractiveTransitionCoordinator.swift in Sources */,
|
||||
884DB95227DE67D900C6A309 /* StoryItemMediaView.swift in Sources */,
|
||||
884DB94F27DE67BB00C6A309 /* StoryPageViewController.swift in Sources */,
|
||||
|
||||
@ -0,0 +1,190 @@
|
||||
//
|
||||
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class StoryHidingManager: Dependencies {
|
||||
|
||||
private let model: StoryViewModel
|
||||
|
||||
init(model: StoryViewModel) {
|
||||
self.model = model
|
||||
}
|
||||
|
||||
public func contextMenuAction(
|
||||
forPresentingController presentingController: UIViewController
|
||||
) -> ContextMenuAction {
|
||||
let isHidden = model.isHidden
|
||||
let title: String
|
||||
let image: UIImage
|
||||
if isHidden {
|
||||
title = NSLocalizedString(
|
||||
"STORIES_UNHIDE_STORY_ACTION",
|
||||
comment: "Context menu action to unhide the selected story"
|
||||
)
|
||||
image = Theme.iconImage(.checkCircle)
|
||||
} else {
|
||||
title = NSLocalizedString(
|
||||
"STORIES_HIDE_STORY_ACTION",
|
||||
comment: "Context menu action to hide the selected story"
|
||||
)
|
||||
image = Theme.iconImage(.xCircle24)
|
||||
}
|
||||
return .init(
|
||||
title: title,
|
||||
image: image,
|
||||
handler: { [self, weak presentingController] _ in
|
||||
guard let presentingController = presentingController else {
|
||||
return
|
||||
}
|
||||
if isHidden {
|
||||
self.setHideStateAndShowToast(shouldHide: !isHidden, presentingController: presentingController)
|
||||
} else {
|
||||
self.showActionSheetIfNeeded(shouldHide: !isHidden, on: presentingController)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func showActionSheetIfNeeded(shouldHide: Bool, on presentingController: UIViewController) {
|
||||
guard shouldHide else {
|
||||
// No need to show anything if unhiding, hide right away
|
||||
setHideStateAndShowToast(shouldHide: shouldHide, presentingController: presentingController)
|
||||
return
|
||||
}
|
||||
|
||||
let actionSheet = createHidingActionSheetWithSneakyTransaction()
|
||||
|
||||
let actionTitle: String
|
||||
if shouldHide {
|
||||
actionTitle = NSLocalizedString(
|
||||
"STORIES_HIDE_STORY_ACTION",
|
||||
comment: "Context menu action to hide the selected story"
|
||||
)
|
||||
} else {
|
||||
actionTitle = NSLocalizedString(
|
||||
"STORIES_UNHIDE_STORY_ACTION",
|
||||
comment: "Context menu action to unhide the selected story"
|
||||
)
|
||||
}
|
||||
|
||||
actionSheet.addAction(ActionSheetAction(
|
||||
title: actionTitle,
|
||||
style: .default,
|
||||
handler: { [self, weak presentingController] _ in
|
||||
guard let presentingController = presentingController else {
|
||||
return
|
||||
}
|
||||
self.setHideStateAndShowToast(shouldHide: shouldHide, presentingController: presentingController)
|
||||
}
|
||||
))
|
||||
actionSheet.addAction(OWSActionSheets.cancelAction)
|
||||
presentingController.presentActionSheet(actionSheet)
|
||||
}
|
||||
|
||||
// MARK: - Loading data
|
||||
|
||||
private func loadThread(_ transaction: SDSAnyReadTransaction) -> TSThread? {
|
||||
switch model.context {
|
||||
case .groupId(let groupId):
|
||||
return TSGroupThread.fetch(groupId: groupId, transaction: transaction)
|
||||
case .authorUuid(let authorUuid):
|
||||
return TSContactThread.getWithContactAddress(
|
||||
authorUuid.asSignalServiceAddress(),
|
||||
transaction: transaction
|
||||
)
|
||||
case .privateStory:
|
||||
owsFailDebug("Unexpectedly had private story when hiding")
|
||||
return nil
|
||||
case .none:
|
||||
owsFailDebug("Unexpectedly missing context for story when hiding")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Header configuration
|
||||
|
||||
private func createHidingActionSheetWithSneakyTransaction() -> ActionSheetController {
|
||||
return ActionSheetController(
|
||||
title: NSLocalizedString(
|
||||
"STORIES_HIDE_STORY_ACTION_SHEET_TITLE",
|
||||
comment: "Title asking the user if they are sure they want to hide stories from another user"
|
||||
),
|
||||
message: loadThreadDisplayNameWithSneakyTransaction().map {
|
||||
String(
|
||||
format: NSLocalizedString(
|
||||
"STORIES_HIDE_STORY_ACTION_SHEET_MESSAGE",
|
||||
comment: "Message asking the user if they are sure they want to hide stories from {{other user's name}}"
|
||||
),
|
||||
$0
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func loadThreadDisplayNameWithSneakyTransaction() -> String? {
|
||||
return Self.databaseStorage.read { transaction -> String? in
|
||||
switch self.model.context {
|
||||
case .groupId(let groupId):
|
||||
return TSGroupThread.fetch(groupId: groupId, transaction: transaction)?.groupNameOrDefault
|
||||
case .authorUuid(let authorUuid):
|
||||
if authorUuid.asSignalServiceAddress().isSystemStoryAddress {
|
||||
return NSLocalizedString(
|
||||
"SYSTEM_ADDRESS_NAME",
|
||||
comment: "Name to display for the 'system' sender, e.g. for release notes and the onboarding story"
|
||||
)
|
||||
}
|
||||
return Self.contactsManager.shortDisplayName(
|
||||
for: authorUuid.asSignalServiceAddress(),
|
||||
transaction: transaction
|
||||
)
|
||||
case .privateStory:
|
||||
owsFailDebug("Unexpectedly had private story when hiding")
|
||||
return nil
|
||||
case .none:
|
||||
owsFailDebug("Unexpectedly missing context for story when hiding")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Issuing changes
|
||||
|
||||
private func setHideStateAndShowToast(
|
||||
shouldHide: Bool,
|
||||
presentingController: UIViewController
|
||||
) {
|
||||
Self.databaseStorage.write { transaction in
|
||||
guard self.model.messages.first?.authorAddress.isSystemStoryAddress != true else {
|
||||
// System stories go through SystemStoryManager
|
||||
Self.systemStoryManager.setSystemStoriesHidden(shouldHide, transaction: transaction)
|
||||
return
|
||||
}
|
||||
guard let thread = self.model.context.thread(transaction: transaction) else {
|
||||
owsFailDebug("Hiding a story without a thread")
|
||||
return
|
||||
}
|
||||
let threadAssociatedData = ThreadAssociatedData.fetchOrDefault(for: thread, transaction: transaction)
|
||||
threadAssociatedData.updateWith(
|
||||
hideStory: shouldHide,
|
||||
updateStorageService: true,
|
||||
transaction: transaction
|
||||
)
|
||||
}
|
||||
let toastText: String
|
||||
if shouldHide {
|
||||
toastText = NSLocalizedString(
|
||||
"STORIES_HIDE_STORY_CONFIRMATION_TOAST",
|
||||
comment: "Toast shown when a story is successfuly hidden"
|
||||
)
|
||||
} else {
|
||||
toastText = NSLocalizedString(
|
||||
"STORIES_UNHIDE_STORY_CONFIRMATION_TOAST",
|
||||
comment: "Toast shown when a story is successfuly unhidden"
|
||||
)
|
||||
}
|
||||
presentingController.presentToast(text: toastText)
|
||||
}
|
||||
}
|
||||
@ -507,21 +507,18 @@ extension StoriesViewController: StoryPageViewControllerDataSource {
|
||||
|
||||
extension StoriesViewController: ContextMenuInteractionDelegate {
|
||||
func contextMenuInteraction(_ interaction: ContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> ContextMenuConfiguration? {
|
||||
guard let indexPath = tableView.indexPathForRow(at: location),
|
||||
let model = model(for: indexPath) else { return nil }
|
||||
guard
|
||||
let indexPath = tableView.indexPathForRow(at: location),
|
||||
let model = model(for: indexPath)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return .init(identifier: indexPath as NSCopying) { _ in
|
||||
|
||||
var actions = [ContextMenuAction]()
|
||||
|
||||
actions.append(.init(
|
||||
title: NSLocalizedString(
|
||||
"STORIES_HIDE_STORY_ACTION",
|
||||
comment: "Context menu action to hide the selected story"),
|
||||
image: Theme.iconImage(.xCircle24),
|
||||
handler: { _ in
|
||||
OWSActionSheets.showActionSheet(title: LocalizationNotNeeded("Hiding stories is not yet implemented."))
|
||||
}))
|
||||
actions.append(StoryHidingManager(model: model).contextMenuAction(forPresentingController: self))
|
||||
|
||||
func appendForwardAction() {
|
||||
actions.append(.init(
|
||||
|
||||
@ -11,6 +11,10 @@ struct StoryViewModel: Dependencies {
|
||||
let messages: [StoryMessage]
|
||||
let hasUnviewedMessages: Bool
|
||||
|
||||
// NOTE: "hidden" stories are still shown,
|
||||
// just in a separate section thats collapsed by default.
|
||||
let isHidden: Bool
|
||||
|
||||
let latestMessageAttachment: StoryThumbnailView.Attachment
|
||||
let hasReplies: Bool
|
||||
let latestMessageName: String
|
||||
@ -44,6 +48,14 @@ struct StoryViewModel: Dependencies {
|
||||
latestMessageAttachment = .from(latestMessage.attachment, transaction: transaction)
|
||||
latestMessageTimestamp = latestMessage.timestamp
|
||||
latestMessageViewedTimestamp = latestMessage.localUserViewedTimestamp
|
||||
|
||||
if latestMessage.authorAddress.isSystemStoryAddress {
|
||||
self.isHidden = Self.systemStoryManager.areSystemStoriesHidden(transaction: transaction)
|
||||
} else {
|
||||
self.isHidden = context.thread(transaction: transaction).map {
|
||||
return ThreadAssociatedData.fetchOrDefault(for: $0, transaction: transaction).hideStory
|
||||
} ?? false
|
||||
}
|
||||
}
|
||||
|
||||
func copy(updatedMessages: [StoryMessage], deletedMessageRowIds: [Int64], transaction: SDSAnyReadTransaction) throws -> Self? {
|
||||
|
||||
@ -6160,6 +6160,15 @@
|
||||
/* Context menu action to hide the selected story */
|
||||
"STORIES_HIDE_STORY_ACTION" = "Hide Story";
|
||||
|
||||
/* Message asking the user if they are sure they want to hide stories from {{other user's name}} */
|
||||
"STORIES_HIDE_STORY_ACTION_SHEET_MESSAGE" = "New story updates from %@ won't appear at the top of the stories list anymore.";
|
||||
|
||||
/* Title asking the user if they are sure they want to hide stories from another user */
|
||||
"STORIES_HIDE_STORY_ACTION_SHEET_TITLE" = "Hide Story?";
|
||||
|
||||
/* Toast shown when a story is successfuly hidden */
|
||||
"STORIES_HIDE_STORY_CONFIRMATION_TOAST" = "Story Hidden";
|
||||
|
||||
/* Indicates that there are no recent stories to render */
|
||||
"STORIES_NO_RECENT_MESSAGES" = "No updates to show right now.\nTap + to add to your story.";
|
||||
|
||||
@ -6205,6 +6214,12 @@
|
||||
/* Title for the stories view. */
|
||||
"STORIES_TITLE" = "Stories";
|
||||
|
||||
/* Context menu action to unhide the selected story */
|
||||
"STORIES_UNHIDE_STORY_ACTION" = "Unhide Story";
|
||||
|
||||
/* Toast shown when a story is successfuly unhidden */
|
||||
"STORIES_UNHIDE_STORY_CONFIRMATION_TOAST" = "Story Unhidden";
|
||||
|
||||
/* Title text for the 'views' tab on the stories views & replies sheet */
|
||||
"STORIES_VIEWS_TAB" = "Views";
|
||||
|
||||
|
||||
@ -85,6 +85,53 @@ public class SystemStoryManager: NSObject, Dependencies, SystemStoryManagerProto
|
||||
}.map(on: queue) { _ in () }
|
||||
}
|
||||
|
||||
// MARK: Hidden State
|
||||
|
||||
private var stateChangeObservers = [SystemStoryStateChangeObserver]()
|
||||
|
||||
public func addStateChangedObserver(_ observer: SystemStoryStateChangeObserver) {
|
||||
stateChangeObservers.append(observer)
|
||||
}
|
||||
|
||||
public func removeStateChangedObserver(_ observer: SystemStoryStateChangeObserver) {
|
||||
stateChangeObservers.removeAll(where: { $0 == observer })
|
||||
}
|
||||
|
||||
public func areSystemStoriesHidden(transaction: SDSAnyReadTransaction) -> Bool {
|
||||
// No need to make this serial with the other calls, db transactions cover us.
|
||||
kvStore.getBool(Constants.kvStoreHiddenStateKey, defaultValue: false, transaction: transaction)
|
||||
}
|
||||
|
||||
public func setSystemStoriesHidden(_ hidden: Bool, transaction: SDSAnyWriteTransaction) {
|
||||
var changedRowIds = [Int64]()
|
||||
defer {
|
||||
DispatchQueue.main.async {
|
||||
self.stateChangeObservers.forEach { $0.systemStoryHiddenStateDidChange(rowIds: changedRowIds) }
|
||||
}
|
||||
}
|
||||
|
||||
// No need to make this serial with the other calls, db transactions cover us.
|
||||
kvStore.setBool(hidden, key: Constants.kvStoreHiddenStateKey, transaction: transaction)
|
||||
|
||||
guard
|
||||
let rawStatus = kvStore.getData(Constants.kvStoreOnboardingStoryStatusKey, transaction: transaction),
|
||||
let onboardingStatus = try? JSONDecoder().decode(DownloadStatus.self, from: rawStatus),
|
||||
let messageUniqueIds = onboardingStatus.messageUniqueIds,
|
||||
!messageUniqueIds.isEmpty
|
||||
else {
|
||||
return
|
||||
}
|
||||
let stories = StoryFinder.listStoriesWithUniqueIds(messageUniqueIds, transaction: transaction)
|
||||
stories.forEach {
|
||||
if hidden {
|
||||
$0.markAsViewed(at: Date().ows_millisecondsSince1970, circumstance: .onThisDevice, transaction: transaction)
|
||||
}
|
||||
if let rowId = $0.id {
|
||||
changedRowIds.append(rowId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Event Observation
|
||||
|
||||
private var isObservingBackgrounding = false
|
||||
@ -159,26 +206,38 @@ public class SystemStoryManager: NSObject, Dependencies, SystemStoryManagerProto
|
||||
}
|
||||
|
||||
private func checkDownloadStatus() -> Promise<DownloadStatus> {
|
||||
return databaseStorage.write(.promise) { [kvStore] transaction in
|
||||
guard
|
||||
let rawStatus = kvStore.getData(Constants.kvStoreStatusKey, transaction: transaction),
|
||||
let status = try? JSONDecoder().decode(DownloadStatus.self, from: rawStatus)
|
||||
else {
|
||||
return .requiresDownload
|
||||
return databaseStorage.write(.promise) { [weak self] transaction in
|
||||
guard let strongSelf = self else {
|
||||
throw OWSAssertionError("SystemStoryManager unretained")
|
||||
}
|
||||
if status.isDownloaded {
|
||||
// clean up opportunistically.
|
||||
try? self.cleanUpStoriesIfNeeded(
|
||||
messageUniqueIds: status.messageUniqueIds,
|
||||
transaction: transaction
|
||||
)
|
||||
}
|
||||
return status
|
||||
return strongSelf.checkDownloadStatus(forceDeleteIfDownloaded: false, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
private func checkDownloadStatus(
|
||||
forceDeleteIfDownloaded: Bool,
|
||||
transaction: SDSAnyWriteTransaction
|
||||
) -> DownloadStatus {
|
||||
guard
|
||||
let rawStatus = kvStore.getData(Constants.kvStoreOnboardingStoryStatusKey, transaction: transaction),
|
||||
let status = try? JSONDecoder().decode(DownloadStatus.self, from: rawStatus)
|
||||
else {
|
||||
return .requiresDownload
|
||||
}
|
||||
if status.isDownloaded {
|
||||
// clean up opportunistically.
|
||||
try? self.cleanUpStoriesIfNeeded(
|
||||
messageUniqueIds: status.messageUniqueIds,
|
||||
forceDeleteIfDownloaded: forceDeleteIfDownloaded,
|
||||
transaction: transaction
|
||||
)
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
private func cleanUpStoriesIfNeeded(
|
||||
messageUniqueIds: [String]?,
|
||||
forceDeleteIfDownloaded: Bool,
|
||||
transaction: SDSAnyWriteTransaction
|
||||
) throws {
|
||||
guard let messageUniqueIds = messageUniqueIds, !messageUniqueIds.isEmpty else {
|
||||
@ -189,11 +248,18 @@ public class SystemStoryManager: NSObject, Dependencies, SystemStoryManagerProto
|
||||
guard !stories.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
var shouldDelete = forceDeleteIfDownloaded
|
||||
|
||||
// If they exist and are expired, delete them.
|
||||
if
|
||||
let minViewTime = stories.lazy.compactMap(\.localUserViewedTimestamp).min(),
|
||||
Date().timeIntervalSince(Date(millisecondsSince1970: minViewTime)) >= Constants.postViewingTimeout
|
||||
{
|
||||
shouldDelete = true
|
||||
}
|
||||
|
||||
if shouldDelete {
|
||||
stories.forEach {
|
||||
$0.sdsRemove(transaction: transaction)
|
||||
}
|
||||
@ -305,13 +371,14 @@ public class SystemStoryManager: NSObject, Dependencies, SystemStoryManagerProto
|
||||
) throws {
|
||||
try kvStore.setData(
|
||||
JSONEncoder().encode(DownloadStatus(messageUniqueIds: messageUniqueIds)),
|
||||
key: Constants.kvStoreStatusKey,
|
||||
key: Constants.kvStoreOnboardingStoryStatusKey,
|
||||
transaction: transaction
|
||||
)
|
||||
}
|
||||
|
||||
internal enum Constants {
|
||||
static let kvStoreStatusKey = "OnboardingStoryStatus"
|
||||
static let kvStoreOnboardingStoryStatusKey = "OnboardingStoryStatus"
|
||||
static let kvStoreHiddenStateKey = "SystemStoriesAreHidden"
|
||||
|
||||
static let manifestPath = "dynamic/ios/stories/onboarding/manifest.json"
|
||||
static let manifestVersionKey = "version"
|
||||
|
||||
@ -24,6 +24,22 @@ public class SystemStoryManagerMock: NSObject, SystemStoryManagerProtocol {
|
||||
public func cleanUpOnboardingStoryIfNeeded() -> Promise<Void> {
|
||||
return cleanUpOnboardingStoryHandler()
|
||||
}
|
||||
|
||||
public func addStateChangedObserver(_ observer: SystemStoryStateChangeObserver) {
|
||||
fatalError("Unimplemented for tests")
|
||||
}
|
||||
|
||||
public func removeStateChangedObserver(_ observer: SystemStoryStateChangeObserver) {
|
||||
fatalError("Unimplemented for tests")
|
||||
}
|
||||
|
||||
public func areSystemStoriesHidden(transaction: SDSAnyReadTransaction) -> Bool {
|
||||
fatalError("Unimplemented for tests")
|
||||
}
|
||||
|
||||
public func setSystemStoriesHidden(_ hidden: Bool, transaction: SDSAnyWriteTransaction) {
|
||||
fatalError("Unimplemented for tests")
|
||||
}
|
||||
}
|
||||
|
||||
public class OnboardingStoryManagerFilesystemMock: OnboardingStoryManagerFilesystem {
|
||||
|
||||
@ -19,4 +19,20 @@ public protocol SystemStoryManagerProtocol: SystemStoryManagerProtocolObjc {
|
||||
/// to be expired, deletes it and cleans up references.
|
||||
/// Called on its own when the app is backgrounded.
|
||||
func cleanUpOnboardingStoryIfNeeded() -> Promise<Void>
|
||||
|
||||
// MARK: Hidden State
|
||||
|
||||
func addStateChangedObserver(_ observer: SystemStoryStateChangeObserver)
|
||||
|
||||
func removeStateChangedObserver(_ observer: SystemStoryStateChangeObserver)
|
||||
|
||||
func areSystemStoriesHidden(transaction: SDSAnyReadTransaction) -> Bool
|
||||
|
||||
/// Sets system stories hidden state. If hiding, marks the onboarding story as viewed.
|
||||
func setSystemStoriesHidden(_ hidden: Bool, transaction: SDSAnyWriteTransaction)
|
||||
}
|
||||
|
||||
public protocol SystemStoryStateChangeObserver: NSObject {
|
||||
|
||||
func systemStoryHiddenStateDidChange(rowIds: [Int64])
|
||||
}
|
||||
|
||||
@ -35,9 +35,16 @@ public class ToastController: NSObject, ToastViewDelegate {
|
||||
owsAssertDebug(edge == .bottom || edge == .top)
|
||||
let offset = (edge == .top) ? inset : -inset
|
||||
|
||||
// Add to the first non-scrollview in the hierarchy, but still pin to the original view.
|
||||
// We don't want the toast to be a subview of any scrollview or it will be subject to scrolling.
|
||||
var parentView = view
|
||||
while parentView is UIScrollView, let superview = view.superview {
|
||||
parentView = superview
|
||||
}
|
||||
|
||||
Logger.debug("")
|
||||
toastView.alpha = 0
|
||||
view.addSubview(toastView)
|
||||
parentView.addSubview(toastView)
|
||||
toastView.setCompressionResistanceHigh()
|
||||
toastView.autoPinEdge(edge, to: edge, of: view, withOffset: offset)
|
||||
toastView.autoPinWidthToSuperview(withMargin: 8)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user