From 97069850f56cebdd633a02a275cc3c7dea68b746 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 31 Jan 2025 17:31:21 +0200 Subject: [PATCH] Ensure multiple mandatory verification flows can be ran consecutively (e.g. following encryption resets) (#3722) * Ensure multiple mandatory verification flows can be ran consecutively (e.g. following encryption resets) * Disabled the back button on the verification screen only when verified and waiting for the security state publisher --- .../OnboardingFlowCoordinator.swift | 25 +++++++++++++------ .../UserSessionFlowCoordinator.swift | 3 ++- .../View/SessionVerificationScreen.swift | 1 + .../Services/Session/UserSession.swift | 1 - 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift index 4a930a5675..3e09b8f702 100644 --- a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift @@ -38,7 +38,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { enum Event: EventType { case next - case nextSkippingIdentityConfimed + case nextSkippingIdentityConfirmed } private let stateMachine: StateMachine @@ -79,6 +79,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { stateMachine = .init(state: .initial) + configureStateMachine() + // Verification can change as part of the onboarding flow by verifying with // another device, using a recovery key or by resetting one's crypto identity. // It can also happen that onboarding started before it had a chance to update, @@ -86,15 +88,14 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { // Handle all those cases here instead of spreading them throughout the code. verificationStateCancellable = userSession.sessionSecurityStatePublisher .map(\.verificationState) - .removeDuplicates() + .receive(on: DispatchQueue.main) .sink { [weak self] value in guard let self, value == .verified, stateMachine.state == .identityConfirmation else { return } appSettings.hasRunIdentityConfirmationOnboarding = true - stateMachine.tryEvent(.nextSkippingIdentityConfimed) - self.verificationStateCancellable = nil + stateMachine.tryEvent(.nextSkippingIdentityConfirmed) } } @@ -111,8 +112,6 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { fatalError("This flow coordinator shouldn't have been started") } - configureStateMachine() - rootNavigationStackCoordinator.setFullScreenCoverCoordinator(navigationStackCoordinator, animated: !isNewLogin) stateMachine.tryEvent(.next) @@ -146,6 +145,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { } private func configureStateMachine() { + stateMachine.addRoute(.init(fromState: .finished, toState: .initial)) stateMachine.addRouteMapping { [weak self] event, fromState, _ in guard let self else { return nil @@ -164,7 +164,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { return .finished case (.identityConfirmation, _, _, _, _): - if event == .nextSkippingIdentityConfimed { + if event == .nextSkippingIdentityConfirmed { // Used when the verification state has updated to verified // after starting the onboarding flow switch (requiresAppLockSetup, requiresAnalyticsSetup, requiresNotificationsSetup) { @@ -225,9 +225,18 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { presentNotificationPermissionsScreen() case (_, _, .finished): rootNavigationStackCoordinator.setFullScreenCoverCoordinator(nil) + stateMachine.tryState(.initial) + case (.finished, _, .initial): + break default: fatalError("Unknown transition: \(context)") } + + if let event = context.event { + MXLog.info("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(event)`") + } else { + MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`") + } } stateMachine.addErrorHandler { context in @@ -251,7 +260,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { presentRecoveryKeyScreen() case .skip: appSettings.hasRunIdentityConfirmationOnboarding = true - stateMachine.tryEvent(.nextSkippingIdentityConfimed) + stateMachine.tryEvent(.nextSkippingIdentityConfirmed) case .reset: startEncryptionResetFlow() case .logout: diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index b2dcc8ef78..8356e0679e 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -220,6 +220,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { } func attemptStartingOnboarding() { + MXLog.info("Attempting to start onboarding") + if onboardingFlowCoordinator.shouldStart { clearRoute(animated: false) onboardingFlowCoordinator.start() @@ -340,7 +342,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { userSession.sessionSecurityStatePublisher .map(\.verificationState) .filter { $0 != .unknown } - .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [weak self] _ in guard let self else { return } diff --git a/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift index 891ba3a66d..b2b6ddb962 100644 --- a/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift @@ -24,6 +24,7 @@ struct SessionVerificationScreen: View { .background() .backgroundStyle(.compound.bgCanvasDefault) .interactiveDismissDisabled() + .navigationBarBackButtonHidden(context.viewState.verificationState == .verified) } // MARK: - Private diff --git a/ElementX/Sources/Services/Session/UserSession.swift b/ElementX/Sources/Services/Session/UserSession.swift index 55ccb3fa23..6bbc0303bd 100644 --- a/ElementX/Sources/Services/Session/UserSession.swift +++ b/ElementX/Sources/Services/Session/UserSession.swift @@ -47,7 +47,6 @@ class UserSession: UserSessionProtocol { MXLog.info("Session security state changed, verificationState: \($0), recoveryState: \($1)") return SessionSecurityState(verificationState: $0, recoveryState: $1) } - .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [weak self] value in self?.sessionSecurityStateSubject.send(value)