diff --git a/Signal/Megaphones/ExperienceUpgradeManager.swift b/Signal/Megaphones/ExperienceUpgradeManager.swift index f4ed7d6a32..a5f9a7f627 100644 --- a/Signal/Megaphones/ExperienceUpgradeManager.swift +++ b/Signal/Megaphones/ExperienceUpgradeManager.swift @@ -8,38 +8,51 @@ import SignalUI class ExperienceUpgradeManager { + private enum StoreKeys { + static let lastExperienceUpgradeDismissDate = "lastExperienceUpgradeDismissDate" + } + private weak static var lastPresented: MegaphoneView? private static let backupSettingsStore = BackupSettingsStore() private static var db: DB { DependenciesBridge.shared.db } private static var deviceStore: OWSDeviceStore { DependenciesBridge.shared.deviceStore } private static let experienceUpgradeStore = ExperienceUpgradeStore() + private static let keyValueStore = NewKeyValueStore(collection: "ExperienceUpgradeManager") private static var remoteConfigManager: RemoteConfigManager { SSKEnvironment.shared.remoteConfigManagerRef } private static var tsAccountManager: TSAccountManager { DependenciesBridge.shared.tsAccountManager } - static func presentNext(fromViewController: UIViewController) -> Bool { - + static func reconcilePresentedExperienceUpgrade(fromViewController: UIViewController) { + let now = Date() var shouldClearNewDeviceNotification = false var shouldClearBackupsEnabledDetails = false - let nextExperienceUpgrade = db.read { tx -> ExperienceUpgrade? in + let lastExperienceUpgradeDismissDate: Date + let nextExperienceUpgrade: ExperienceUpgrade? + ( + lastExperienceUpgradeDismissDate, + nextExperienceUpgrade, + ) = db.read { tx in guard let registeredState = try? tsAccountManager.registeredState(tx: tx), let registrationDate = tsAccountManager.registrationDate(tx: tx) else { - return nil + return (.distantPast, nil) } - let now = Date() - let timeIntervalSinceRegistration = now.timeIntervalSince(registrationDate) + let lastExperienceUpgradeDismissDate = keyValueStore.fetchValue( + Date.self, + forKey: StoreKeys.lastExperienceUpgradeDismissDate, + tx: tx, + ) ?? .distantPast - return allKnownExperienceUpgrades(transaction: tx) + let nextExperienceUpgrade = allKnownExperienceUpgrades(transaction: tx) .first { upgrade in guard !upgrade.isComplete, !upgrade.isSnoozed(now: now), !upgrade.hasPassedNumberOfDaysToShow(now: now), - timeIntervalSinceRegistration > upgrade.manifest.delayAfterRegistration, + now.timeIntervalSince(registrationDate) > upgrade.manifest.delayAfterRegistration, now < upgrade.manifest.expirationDate, (registeredState.isPrimary || upgrade.manifest.showOnLinkedDevices) else { @@ -114,6 +127,11 @@ class ExperienceUpgradeManager { return false } } + + return ( + lastExperienceUpgradeDismissDate, + nextExperienceUpgrade, + ) } if shouldClearNewDeviceNotification { @@ -129,22 +147,24 @@ class ExperienceUpgradeManager { } guard let nextExperienceUpgrade else { - dismissLastPresented() - return false + _ = dismissLastPresented(now: now) + return } if let lastPresented, lastPresented.experienceUpgrade.manifest == nextExperienceUpgrade.manifest { - return true + return } - // Otherwise, dismiss any currently present experience upgrade. It's - // no longer next and may have been completed. - dismissLastPresented() + // If we're dismissing a megaphone, don't immediately present another. + if dismissLastPresented(now: now) { + return + } if + now.timeIntervalSince(lastExperienceUpgradeDismissDate) > .day, let megaphone = self.megaphone( forExperienceUpgrade: nextExperienceUpgrade, fromViewController: fromViewController, @@ -159,10 +179,6 @@ class ExperienceUpgradeManager { tx: tx, ) } - - return true - } else { - return false } } @@ -200,22 +216,24 @@ class ExperienceUpgradeManager { return ExperienceUpgradeManifest.sortedByImportance(experienceUpgrades) } - // MARK: - - - static func dismissLastPresented(ifMatching manifest: ExperienceUpgradeManifest? = nil) { + /// - Returns + /// Whether or not we dismissed a megaphone. + private static func dismissLastPresented(now: Date) -> Bool { guard let lastPresented else { - return + return false } - if - let manifest, - lastPresented.experienceUpgrade.manifest != manifest - { - return + db.write { tx in + keyValueStore.writeValue( + now, + forKey: StoreKeys.lastExperienceUpgradeDismissDate, + tx: tx, + ) } lastPresented.dismiss(animated: false, completion: nil) self.lastPresented = nil + return true } // MARK: - diff --git a/Signal/Megaphones/UserInterface/BackupEnablementMegaphone.swift b/Signal/Megaphones/UserInterface/BackupEnablementMegaphone.swift index 95f7e2ea32..3fb85139ea 100644 --- a/Signal/Megaphones/UserInterface/BackupEnablementMegaphone.swift +++ b/Signal/Megaphones/UserInterface/BackupEnablementMegaphone.swift @@ -36,7 +36,6 @@ class BackupEnablementMegaphone: MegaphoneView { let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { [weak self] in SignalApp.shared.showAppSettings(mode: .backups()) self?.markAsSnoozedWithSneakyTransaction() - self?.dismiss(animated: true) } let secondaryButton = snoozeButton( diff --git a/Signal/Megaphones/UserInterface/BackupsEnabledNotificationMegaphone.swift b/Signal/Megaphones/UserInterface/BackupsEnabledNotificationMegaphone.swift index 5255f0fb58..0f2e4d0f68 100644 --- a/Signal/Megaphones/UserInterface/BackupsEnabledNotificationMegaphone.swift +++ b/Signal/Megaphones/UserInterface/BackupsEnabledNotificationMegaphone.swift @@ -42,13 +42,11 @@ class BackupsEnabledNotificationMegaphone: MegaphoneView { ) let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { [weak self] in SignalApp.shared.showAppSettings(mode: .backups()) - self?.markAsViewed() - self?.dismiss(animated: true) + self?.stopShowing() } let secondaryButton = MegaphoneView.Button(title: CommonStrings.okButton) { [weak self] in - self?.markAsViewed() - self?.dismiss(animated: true) + self?.stopShowing() } buttons = [primaryButton, secondaryButton] @@ -58,9 +56,11 @@ class BackupsEnabledNotificationMegaphone: MegaphoneView { fatalError("init(coder:) has not been implemented") } - private func markAsViewed() { + private func stopShowing() { db.write { tx in backupSettingsStore.clearLastBackupEnabledDetails(tx: tx) } + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } } diff --git a/Signal/Megaphones/UserInterface/ContactPermissionReminderMegaphone.swift b/Signal/Megaphones/UserInterface/ContactPermissionReminderMegaphone.swift index d09b1f5362..7fcf869106 100644 --- a/Signal/Megaphones/UserInterface/ContactPermissionReminderMegaphone.swift +++ b/Signal/Megaphones/UserInterface/ContactPermissionReminderMegaphone.swift @@ -7,8 +7,6 @@ import SignalServiceKit import SignalUI class ContactPermissionReminderMegaphone: MegaphoneView { - weak var actionSheetController: ActionSheetController? - init(experienceUpgrade: ExperienceUpgrade, fromViewController: UIViewController) { super.init(experienceUpgrade: experienceUpgrade) @@ -27,8 +25,9 @@ class ContactPermissionReminderMegaphone: MegaphoneView { comment: "Action text for contact permission reminder megaphone", ) - let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { [weak self] in - guard let self else { return } + let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { + let actionSheetController = ActionSheetController() + actionSheetController.isCancelable = true let turnOnView: TurnOnPermissionView if #available(iOS 18, *) { @@ -37,6 +36,7 @@ class ContactPermissionReminderMegaphone: MegaphoneView { .withRenderingMode(.alwaysTemplate) turnOnView = TurnOnPermissionView( + fromActionSheetController: actionSheetController, title: OWSLocalizedString( "CONTACT_PERMISSION_ACTION_SHEET_2_TITLE", comment: "Title for contact permission action sheet", @@ -71,6 +71,7 @@ class ContactPermissionReminderMegaphone: MegaphoneView { ) } else { turnOnView = TurnOnPermissionView( + fromActionSheetController: actionSheetController, title: OWSLocalizedString( "CONTACT_PERMISSION_ACTION_SHEET_TITLE", comment: "Title for contact permission action sheet", @@ -98,11 +99,8 @@ class ContactPermissionReminderMegaphone: MegaphoneView { ) } - let actionSheetController = ActionSheetController() actionSheetController.customHeader = turnOnView - actionSheetController.isCancelable = true fromViewController.presentActionSheet(actionSheetController) - self.actionSheetController = actionSheetController } let secondaryButton = snoozeButton( @@ -119,9 +117,4 @@ class ContactPermissionReminderMegaphone: MegaphoneView { required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func dismiss(animated: Bool = true, completion: (() -> Void)? = nil) { - super.dismiss(animated: animated, completion: completion) - actionSheetController?.dismiss(animated: animated) - } } diff --git a/Signal/Megaphones/UserInterface/CreateUsernameMegaphone.swift b/Signal/Megaphones/UserInterface/CreateUsernameMegaphone.swift index 624ee1dc53..5c43588d83 100644 --- a/Signal/Megaphones/UserInterface/CreateUsernameMegaphone.swift +++ b/Signal/Megaphones/UserInterface/CreateUsernameMegaphone.swift @@ -56,15 +56,10 @@ class CreateUsernameMegaphone: MegaphoneView { private func onSetUpTapped(fromViewController: UIViewController) { markAsSnoozedWithSneakyTransaction() - - dismiss(animated: true) { - self.usernameSelectionCoordinator.present(fromViewController: fromViewController) - } + usernameSelectionCoordinator.present(fromViewController: fromViewController) } private func onNotNowTapped() { markAsSnoozedWithSneakyTransaction() - - dismiss(animated: true) } } diff --git a/Signal/Megaphones/UserInterface/InactiveLinkedDeviceReminderMegaphone.swift b/Signal/Megaphones/UserInterface/InactiveLinkedDeviceReminderMegaphone.swift index ce06f8f266..ef12634894 100644 --- a/Signal/Megaphones/UserInterface/InactiveLinkedDeviceReminderMegaphone.swift +++ b/Signal/Megaphones/UserInterface/InactiveLinkedDeviceReminderMegaphone.swift @@ -7,10 +7,6 @@ import SignalServiceKit import UIKit final class InactiveLinkedDeviceReminderMegaphone: MegaphoneView { - private var inactiveLinkedDeviceFinder: InactiveLinkedDeviceFinder { - DependenciesBridge.shared.inactiveLinkedDeviceFinder - } - private let inactiveLinkedDevice: InactiveLinkedDevice /// The number of days until the linked device represented by this megaphone @@ -57,15 +53,14 @@ final class InactiveLinkedDeviceReminderMegaphone: MegaphoneView { "INACTIVE_LINKED_DEVICE_REMINDER_MEGAPHONE_DONT_REMIND_ME_BUTTON", comment: "Title for a button in an in-app megaphone about a user's inactive linked device, indicating the user doesn't want to be reminded.", )) { - DependenciesBridge.shared.db.asyncWrite( - block: { tx in - self.inactiveLinkedDeviceFinder.permanentlyDisableFinders(tx: tx) - }, - completionQueue: .main, - completion: { [weak self] in - self?.dismiss() - }, - ) + let db = DependenciesBridge.shared.db + let inactiveLinkedDeviceFinder = DependenciesBridge.shared.inactiveLinkedDeviceFinder + + db.write { tx in + inactiveLinkedDeviceFinder.permanentlyDisableFinders(tx: tx) + } + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } let gotItButton = snoozeButton( fromViewController: fromViewController, diff --git a/Signal/Megaphones/UserInterface/MegaphoneView.swift b/Signal/Megaphones/UserInterface/MegaphoneView.swift index 680d27d2cd..d0cd3e6b2d 100644 --- a/Signal/Megaphones/UserInterface/MegaphoneView.swift +++ b/Signal/Megaphones/UserInterface/MegaphoneView.swift @@ -60,12 +60,10 @@ class MegaphoneView: UIView { guard !hasPresented else { return owsFailDebug("can only present once") } - guard titleText != nil, bodyText != nil else { - return owsFailDebug("megaphone is not prepared for presentation") + guard titleText != nil, bodyText != nil, !buttons.isEmpty else { + owsFail("Megaphone missing required properties!") } - // Top section - let labelStack = createLabelStack() let topStackSubviews: [UIView] @@ -82,22 +80,7 @@ class MegaphoneView: UIView { topStackView.layoutMargins = UIEdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12) stackView.addArrangedSubview(topStackView) - - // Buttons - - if buttons.count > 0 { - stackView.addArrangedSubview(createButtonsStack()) - } else { - let dismissButton = UIButton() - dismissButton.setTemplateImage(Theme.iconImage(.buttonX), tintColor: Theme.darkThemePrimaryColor) - dismissButton.addTarget(self, action: #selector(tappedDismiss), for: .touchUpInside) - - addSubview(dismissButton) - - dismissButton.autoSetDimensions(to: CGSize(square: 40)) - dismissButton.autoPinEdge(toSuperviewEdge: .trailing) - dismissButton.autoPinEdge(toSuperviewEdge: .top) - } + stackView.addArrangedSubview(createButtonsStack()) fromViewController.view.addSubview(self) autoPinEdge(toSuperviewSafeArea: .leading, withInset: 8) @@ -128,11 +111,6 @@ class MegaphoneView: UIView { darkThemeBackgroundOverlay.isHidden = !Theme.isDarkThemeEnabled } - @objc - private func tappedDismiss() { - dismiss() - } - private func createLabelStack() -> UIStackView { let titleLabel = UILabel() titleLabel.numberOfLines = 0 @@ -216,18 +194,18 @@ class MegaphoneView: UIView { divider.autoPinWidthToSuperview() divider.autoPinHeightToSuperview(withMargin: 8) default: - owsFailDebug("only supports 1 or 2 buttons") + owsFail("Megaphones must have one or two buttons!") } return buttonsStack } func snoozeButton(fromViewController: UIViewController, snoozeTitle: String = MegaphoneStrings.remindMeLater) -> Button { - return Button(title: snoozeTitle) { [weak self] in - self?.markAsSnoozedWithSneakyTransaction() - self?.dismiss { - self?.presentToast(text: MegaphoneStrings.weWillRemindYouLater, fromViewController: fromViewController) - } + return Button(title: snoozeTitle) { [weak self, weak fromViewController] in + guard let self, let fromViewController else { return } + + markAsSnoozedWithSneakyTransaction() + presentToast(text: MegaphoneStrings.weWillRemindYouLater, fromViewController: fromViewController) } } @@ -243,6 +221,8 @@ class MegaphoneView: UIView { tx: tx, ) } + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } func markAsCompleteWithSneakyTransaction() { @@ -255,5 +235,7 @@ class MegaphoneView: UIView { tx: tx, ) } + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } } diff --git a/Signal/Megaphones/UserInterface/NewLinkedDeviceNotificationMegaphone.swift b/Signal/Megaphones/UserInterface/NewLinkedDeviceNotificationMegaphone.swift index 0e61d59f7b..746d1130f1 100644 --- a/Signal/Megaphones/UserInterface/NewLinkedDeviceNotificationMegaphone.swift +++ b/Signal/Megaphones/UserInterface/NewLinkedDeviceNotificationMegaphone.swift @@ -45,15 +45,13 @@ final class NewLinkedDeviceNotificationMegaphone: MegaphoneView { ), ) { [weak self] in SignalApp.shared.showAppSettings(mode: .linkedDevices) - self?.markAsViewed() - self?.dismiss() + self?.stopShowing() } let acknowledgeButton = Button( title: CommonStrings.acknowledgeButton, ) { [weak self] in - self?.markAsViewed() - self?.dismiss() + self?.stopShowing() } buttons = [acknowledgeButton, viewDeviceButton] @@ -64,9 +62,11 @@ final class NewLinkedDeviceNotificationMegaphone: MegaphoneView { fatalError("init(coder:) has not been implemented") } - private func markAsViewed() { + private func stopShowing() { db.write { tx in deviceStore.clearMostRecentlyLinkedDeviceDetails(tx: tx) } + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } } diff --git a/Signal/Megaphones/UserInterface/NotificationPermissionReminderMegaphone.swift b/Signal/Megaphones/UserInterface/NotificationPermissionReminderMegaphone.swift index 42999b145b..7cfc358b3c 100644 --- a/Signal/Megaphones/UserInterface/NotificationPermissionReminderMegaphone.swift +++ b/Signal/Megaphones/UserInterface/NotificationPermissionReminderMegaphone.swift @@ -7,8 +7,6 @@ import SignalServiceKit import SignalUI class NotificationPermissionReminderMegaphone: MegaphoneView { - weak var actionSheetController: ActionSheetController? - init(experienceUpgrade: ExperienceUpgrade, fromViewController: UIViewController) { super.init(experienceUpgrade: experienceUpgrade) @@ -27,10 +25,12 @@ class NotificationPermissionReminderMegaphone: MegaphoneView { comment: "Action text for notification permission reminder megaphone", ) - let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { [weak self] in - guard let self else { return } + let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { + let actionSheetController = ActionSheetController() + actionSheetController.isCancelable = true let turnOnView = TurnOnPermissionView( + fromActionSheetController: actionSheetController, title: OWSLocalizedString( "NOTIFICATION_PERMISSION_ACTION_SHEET_TITLE", comment: "Title for notification permission action sheet", @@ -64,11 +64,8 @@ class NotificationPermissionReminderMegaphone: MegaphoneView { ], ) - let actionSheetController = ActionSheetController() actionSheetController.customHeader = turnOnView - actionSheetController.isCancelable = true fromViewController.presentActionSheet(actionSheetController) - self.actionSheetController = actionSheetController } let secondaryButton = snoozeButton( @@ -85,20 +82,22 @@ class NotificationPermissionReminderMegaphone: MegaphoneView { required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func dismiss(animated: Bool = true, completion: (() -> Void)? = nil) { - super.dismiss(animated: animated, completion: completion) - actionSheetController?.dismiss(animated: animated) - } } +// MARK: - + class TurnOnPermissionView: UIStackView { struct Step { let icon: UIImage? let text: String } - init(title: String, message: String, steps: [Step], button: UIButton? = nil) { + init( + fromActionSheetController: ActionSheetController, + title: String, + message: String, + steps: [Step], + ) { super.init(frame: .zero) axis = .vertical @@ -122,10 +121,14 @@ class TurnOnPermissionView: UIStackView { } // Button - let primaryButton = button ?? UIButton( + let primaryButton = UIButton( configuration: .largePrimary(title: CommonStrings.goToSettingsButton), - primaryAction: UIAction { [weak self] _ in - self?.goToSettings() + primaryAction: UIAction { [weak self, weak fromActionSheetController] _ in + guard let self, let fromActionSheetController else { return } + + fromActionSheetController.dismiss(animated: true) { + self.goToSettings() + } }, ) let buttonContainer = UIView.container() diff --git a/Signal/Megaphones/UserInterface/PinReminderMegaphone.swift b/Signal/Megaphones/UserInterface/PinReminderMegaphone.swift index 482ac065ba..06b28c799d 100644 --- a/Signal/Megaphones/UserInterface/PinReminderMegaphone.swift +++ b/Signal/Megaphones/UserInterface/PinReminderMegaphone.swift @@ -17,30 +17,31 @@ class PinReminderMegaphone: MegaphoneView { let primaryButtonTitle = OWSLocalizedString("PIN_REMINDER_MEGAPHONE_ACTION", comment: "Action text for PIN reminder megaphone") - let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { [weak self] in - let vc = PinReminderViewController { result in + let primaryButton = MegaphoneView.Button(title: primaryButtonTitle) { [weak fromViewController] in + guard let fromViewController else { return } + + let vc = PinReminderViewController { [weak self] pinReminderViewController, result in // Always dismiss the PIN reminder view (we dismiss the *megaphone* later). - fromViewController.dismiss(animated: true) + pinReminderViewController.dismiss(animated: true) guard let self else { return } + switch result { case .succeeded: - self.dismiss(animated: false) - self.presentToastForNewRepetitionInterval( + presentToastForNewRepetitionInterval( wasSuccessful: true, fromViewController: fromViewController, ) case .canceled(didGuessWrong: true): - self.dismiss(animated: false) - self.presentToastForNewRepetitionInterval( + presentToastForNewRepetitionInterval( wasSuccessful: false, fromViewController: fromViewController, ) - case .changedPin: - self.dismiss(animated: false) - case .canceled(didGuessWrong: false): + case .changedPin, .canceled(didGuessWrong: false): break } + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } fromViewController.present(vc, animated: true) diff --git a/Signal/Megaphones/UserInterface/RecoveryKeyReminderMegaphone.swift b/Signal/Megaphones/UserInterface/RecoveryKeyReminderMegaphone.swift index 13570a59b9..61d87d6b30 100644 --- a/Signal/Megaphones/UserInterface/RecoveryKeyReminderMegaphone.swift +++ b/Signal/Megaphones/UserInterface/RecoveryKeyReminderMegaphone.swift @@ -45,12 +45,20 @@ class RecoveryKeyReminderMegaphone: MegaphoneView { BackupRecoveryKeyReminderCoordinator( aep: aep, fromViewController: fromViewController, - onSuccess: { - self.dismiss() - self.presentToastForNewRepetitionInterval(fromViewController: fromViewController) + onSuccess: { [weak self] in + guard let self else { return } + db.write { tx in backupSettingsStore.setLastRecoveryKeyReminderDate(Date(), tx: tx) } + + let toastText = OWSLocalizedString( + "BACKUP_KEY_REMINDER_SUCCESSFUL_TOAST", + comment: "Toast indicating that the Recovery Key was correct.", + ) + presentToast(text: toastText, fromViewController: fromViewController) + + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) }, ).presentVerifyFlow() } @@ -66,13 +74,4 @@ class RecoveryKeyReminderMegaphone: MegaphoneView { required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - func presentToastForNewRepetitionInterval(fromViewController: UIViewController) { - let toastText = OWSLocalizedString( - "BACKUP_KEY_REMINDER_SUCCESSFUL_TOAST", - comment: "Toast indicating that the Recovery Key was correct.", - ) - - presentToast(text: toastText, fromViewController: fromViewController) - } } diff --git a/Signal/Megaphones/UserInterface/RemoteMegaphone.swift b/Signal/Megaphones/UserInterface/RemoteMegaphone.swift index 2054255166..9dd9b2e0e9 100644 --- a/Signal/Megaphones/UserInterface/RemoteMegaphone.swift +++ b/Signal/Megaphones/UserInterface/RemoteMegaphone.swift @@ -80,16 +80,13 @@ class RemoteMegaphone: MegaphoneView { switch action { case .snooze: markAsSnoozedWithSneakyTransaction() - dismiss() case .finish: markAsCompleteWithSneakyTransaction() - dismiss() case .donate: let done = { [weak self] in guard let self else { return } // Snooze regardless of outcome. self.markAsSnoozedWithSneakyTransaction() - self.dismiss(animated: false) } guard @@ -134,7 +131,6 @@ class RemoteMegaphone: MegaphoneView { guard let self else { return } // Snooze regardless of outcome. self.markAsSnoozedWithSneakyTransaction() - self.dismiss(animated: false) } guard @@ -153,7 +149,6 @@ class RemoteMegaphone: MegaphoneView { fromViewController.present(navController, animated: true, completion: done) case .unrecognized(let actionId): owsFailDebug("Unrecognized action with ID \(actionId) should never have made it into \(buttonDescriptor) button!") - dismiss() } } } diff --git a/Signal/src/ViewControllers/AppSettings/Account/AccountSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Account/AccountSettingsViewController.swift index d112de4a96..346feace35 100644 --- a/Signal/src/ViewControllers/AppSettings/Account/AccountSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Account/AccountSettingsViewController.swift @@ -355,7 +355,7 @@ class AccountSettingsViewController: OWSTableViewController2 { SSKEnvironment.shared.ows2FAManagerRef.setAreRemindersEnabled(false, transaction: transaction) } - ExperienceUpgradeManager.dismissLastPresented(ifMatching: .pinReminder) + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } else { self.updateTableContents() } diff --git a/Signal/src/ViewControllers/AppSettings/Linked Devices/LinkedDevicesView.swift b/Signal/src/ViewControllers/AppSettings/Linked Devices/LinkedDevicesView.swift index 75fb929f94..21bbbbb6b1 100644 --- a/Signal/src/ViewControllers/AppSettings/Linked Devices/LinkedDevicesView.swift +++ b/Signal/src/ViewControllers/AppSettings/Linked Devices/LinkedDevicesView.swift @@ -355,7 +355,7 @@ extension LinkedDevicesViewModel: LinkDeviceViewControllerDelegate { deviceStore.clearMostRecentlyLinkedDeviceDetails(tx: tx) } - ExperienceUpgradeManager.dismissLastPresented(ifMatching: .newLinkedDeviceNotification) + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } SSKEnvironment.shared.notificationPresenterRef.clearDeliveredNewLinkedDevicesNotifications() diff --git a/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController+Notifications.swift b/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController+Notifications.swift index a218ebd0e7..e51922bf2a 100644 --- a/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController+Notifications.swift +++ b/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController+Notifications.swift @@ -111,8 +111,8 @@ extension ChatListViewController { ) NotificationCenter.default.addObserver( self, - selector: #selector(reloadExperienceUpgrades), - name: .inactivePrimaryDeviceChanged, + selector: #selector(reconcileExperienceUpgrades), + name: .megaphoneStateDidChange, object: nil, ) NotificationCenter.default.addObserver( @@ -262,7 +262,7 @@ extension ChatListViewController { private func applicationDidBecomeActive(_ notification: NSNotification) { AssertIsOnMainThread() - showExperienceUpgradeIfNecessary() + reconcileExperienceUpgrades() updateShouldBeUpdatingView() } @@ -341,13 +341,6 @@ extension ChatListViewController { updateUsernameReminderView() loadCoordinator.loadIfNecessary() } - - @objc - private func reloadExperienceUpgrades() { - AssertIsOnMainThread() - - showExperienceUpgradeIfNecessary() - } } // MARK: - Notifications diff --git a/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift b/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift index e8b4a69102..ab89ed6b72 100644 --- a/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift +++ b/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift @@ -239,7 +239,8 @@ public class ChatListViewController: OWSViewController, HomeTabViewController { appReadiness.setUIIsReady() - showExperienceUpgradeIfNecessary() + presentGetStartedBannerIfNecessary() + reconcileExperienceUpgrades() requestReviewIfAppropriate() showFYISheetIfNecessary() @@ -360,12 +361,9 @@ public class ChatListViewController: OWSViewController, HomeTabViewController { // MARK: - Experience Upgrades - /// Show an "experience ugprade" if necessary; this might be an `ExperienceUpgrade` - /// proper, or other "onboarding" UX. - func showExperienceUpgradeIfNecessary() { - if !ExperienceUpgradeManager.presentNext(fromViewController: self) { - presentGetStartedBannerIfNecessary() - } + @objc + func reconcileExperienceUpgrades() { + ExperienceUpgradeManager.reconcilePresentedExperienceUpgrade(fromViewController: self) } // MARK: - FYI sheets diff --git a/Signal/src/ViewControllers/PinReminderViewController.swift b/Signal/src/ViewControllers/PinReminderViewController.swift index ce16cc80e7..b721d39dab 100644 --- a/Signal/src/ViewControllers/PinReminderViewController.swift +++ b/Signal/src/ViewControllers/PinReminderViewController.swift @@ -14,7 +14,7 @@ public class PinReminderViewController: OWSViewController { case succeeded } - private let completionHandler: ((PinReminderResult) -> Void)? + private let completionHandler: (PinReminderViewController, PinReminderResult) -> Void private let contentView = UIView() private var contentViewBottomEdgeConstraint: NSLayoutConstraint? @@ -94,7 +94,7 @@ public class PinReminderViewController: OWSViewController { private let context: ViewControllerContext - init(completionHandler: ((PinReminderResult) -> Void)? = nil) { + init(completionHandler: @escaping (PinReminderViewController, PinReminderResult) -> Void) { self.context = ViewControllerContext.shared self.completionHandler = completionHandler super.init() @@ -341,7 +341,7 @@ public class PinReminderViewController: OWSViewController { showCancelButton: true, onSuccess: { [weak self] _ in guard let self else { return } - completionHandler?(.changedPin) + completionHandler(self, .changedPin) }, ) present(OWSNavigationController(rootViewController: viewController), animated: true) @@ -355,7 +355,7 @@ public class PinReminderViewController: OWSViewController { // If they didn't try and enter a PIN, we do nothing and leave the megaphone. if hasGuessedWrong { SSKEnvironment.shared.ows2FAManagerRef.reminderCompleted(incorrectAttempts: true) } - self.completionHandler?(.canceled(didGuessWrong: hasGuessedWrong)) + completionHandler(self, .canceled(didGuessWrong: hasGuessedWrong)) } private func submitPressed() { @@ -382,9 +382,9 @@ public class PinReminderViewController: OWSViewController { let success = db.read { tx in twoFactorManager.verifyPin(pin, tx: tx) } if success { twoFactorManager.reminderCompleted(incorrectAttempts: self.hasGuessedWrong) - self.completionHandler?(.succeeded) + completionHandler(self, .succeeded) } else if !silent { - self.validationState = .mismatch + validationState = .mismatch } } diff --git a/Signal/src/ViewControllers/PinSetupViewController.swift b/Signal/src/ViewControllers/PinSetupViewController.swift index b3a7826fca..c00f87970c 100644 --- a/Signal/src/ViewControllers/PinSetupViewController.swift +++ b/Signal/src/ViewControllers/PinSetupViewController.swift @@ -526,7 +526,7 @@ public class PinSetupViewController: OWSViewController, OWSNavigationChildContro accountAttributesUpdater.scheduleAccountAttributesUpdate(authedAccount: .implicit(), tx: tx) } - ExperienceUpgradeManager.dismissLastPresented(ifMatching: .introducingPins) + NotificationCenter.default.post(name: .megaphoneStateDidChange, object: nil) } } diff --git a/SignalServiceKit/Devices/InactivePrimaryDeviceStore.swift b/SignalServiceKit/Devices/InactivePrimaryDeviceStore.swift index 1256d80d3f..089ff187ac 100644 --- a/SignalServiceKit/Devices/InactivePrimaryDeviceStore.swift +++ b/SignalServiceKit/Devices/InactivePrimaryDeviceStore.swift @@ -3,10 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // -extension Notification.Name { - public static let inactivePrimaryDeviceChanged = Notification.Name("inactivePrimaryDeviceChanged") -} - public class InactivePrimaryDeviceStore: NSObject { private enum StoreKeys { static let hasInactivePrimaryDeviceAlert: String = "hasInactivePrimaryDevice" diff --git a/SignalServiceKit/Megaphones/ExperienceUpgradeStore.swift b/SignalServiceKit/Megaphones/ExperienceUpgradeStore.swift index d1c36c7415..9834636147 100644 --- a/SignalServiceKit/Megaphones/ExperienceUpgradeStore.swift +++ b/SignalServiceKit/Megaphones/ExperienceUpgradeStore.swift @@ -3,6 +3,12 @@ // SPDX-License-Identifier: AGPL-3.0-only // +extension Notification.Name { + public static let megaphoneStateDidChange = Notification.Name("ExperienceUpgradeManager.MegaphoneStateDidChange") +} + +// MARK: - + public struct ExperienceUpgradeStore { public init() {} diff --git a/SignalServiceKit/Network/OWSChatConnection.swift b/SignalServiceKit/Network/OWSChatConnection.swift index 3b8e47afd9..5bc3cda6d2 100644 --- a/SignalServiceKit/Network/OWSChatConnection.swift +++ b/SignalServiceKit/Network/OWSChatConnection.swift @@ -1141,7 +1141,7 @@ class OWSAuthConnectionUsingLibSignal: OWSChatConnectionUsingLibSignal