Don't show a megaphone for 1d after dismissing previous

This commit is contained in:
Sasha Weiss 2026-06-01 12:51:35 -07:00 committed by GitHub
parent 0cc18a5285
commit 8b1379149c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 147 additions and 174 deletions

View File

@ -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: -

View File

@ -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(

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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() {}

View File

@ -1141,7 +1141,7 @@ class OWSAuthConnectionUsingLibSignal: OWSChatConnectionUsingLibSignal<Authentic
// Megaphones might load before we setup the OWSChatConnection
// so we should notify the UI that the value has changed.
NotificationCenter.default.postOnMainThread(name: .inactivePrimaryDeviceChanged, object: nil)
NotificationCenter.default.postOnMainThread(name: .megaphoneStateDidChange, object: nil)
}
func chatConnection(_ chat: AuthenticatedChatConnection, didReceiveAlerts alerts: [String]) {