mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Onboarding flow coordinator and FTUE changes (#2578)
Fixes #2595, fixes #2594, fixes #2593, fixes #2592, fixes #2591
This commit is contained in:
@@ -24,7 +24,6 @@ protocol AuthenticationFlowCoordinatorDelegate: AnyObject {
|
||||
|
||||
class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let authenticationService: AuthenticationServiceProxyProtocol
|
||||
private let appLockService: AppLockServiceProtocol
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
@@ -36,23 +35,18 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private var oidcPresenter: OIDCAuthenticationPresenter?
|
||||
|
||||
// periphery: ignore - used to store the coordinator to avoid deallocation
|
||||
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
|
||||
|
||||
// periphery:ignore - retaining purpose
|
||||
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
||||
|
||||
weak var delegate: AuthenticationFlowCoordinatorDelegate?
|
||||
|
||||
init(authenticationService: AuthenticationServiceProxyProtocol,
|
||||
appLockService: AppLockServiceProtocol,
|
||||
bugReportService: BugReportServiceProtocol,
|
||||
navigationRootCoordinator: NavigationRootCoordinator,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.authenticationService = authenticationService
|
||||
self.appLockService = appLockService
|
||||
self.bugReportService = bugReportService
|
||||
self.navigationRootCoordinator = navigationRootCoordinator
|
||||
self.appSettings = appSettings
|
||||
@@ -108,7 +102,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private func showReportProblemScreen() {
|
||||
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(navigationStackCoordinator),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
userIndicatorController: userIndicatorController,
|
||||
bugReportService: bugReportService,
|
||||
userID: nil,
|
||||
deviceID: nil))
|
||||
@@ -265,58 +259,10 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
||||
appSettings.lastLoginDate = .now
|
||||
|
||||
if appSettings.appLockIsMandatory, !appLockService.isEnabled {
|
||||
showAppLockSetupFlow(userSession: userSession)
|
||||
} else if analytics.shouldShowAnalyticsPrompt {
|
||||
showAnalyticsPrompt(userSession: userSession)
|
||||
} else {
|
||||
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
||||
}
|
||||
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
||||
}
|
||||
|
||||
private func showAppLockSetupFlow(userSession: UserSessionProtocol) {
|
||||
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding,
|
||||
appLockService: appLockService,
|
||||
navigationStackCoordinator: navigationStackCoordinator)
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .complete:
|
||||
appLockFlowCoordinator = nil
|
||||
if analytics.shouldShowAnalyticsPrompt {
|
||||
showAnalyticsPrompt(userSession: userSession)
|
||||
} else {
|
||||
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
||||
}
|
||||
case .forceLogout:
|
||||
fatalError("The PIN creation flow should not fail.")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
appLockFlowCoordinator = coordinator
|
||||
coordinator.start()
|
||||
}
|
||||
|
||||
private func showAnalyticsPrompt(userSession: UserSessionProtocol) {
|
||||
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analytics, termsURL: appSettings.analyticsConfiguration.termsURL)
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .done:
|
||||
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
|
||||
private static let loadingIndicatorIdentifier = "authenticationFlowCoordinatorLoading"
|
||||
private static let loadingIndicatorIdentifier = "\(AuthenticationFlowCoordinator.self)-Loading"
|
||||
|
||||
private func startLoading() {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import SwiftState
|
||||
|
||||
class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let userSession: UserSessionProtocol
|
||||
private let appLockService: AppLockServiceProtocol
|
||||
private let analyticsService: AnalyticsService
|
||||
private let appSettings: AppSettings
|
||||
private let notificationManager: NotificationManagerProtocol
|
||||
private let rootNavigationStackCoordinator: NavigationStackCoordinator
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let isNewLogin: Bool
|
||||
|
||||
private var navigationStackCoordinator: NavigationStackCoordinator!
|
||||
|
||||
enum State: StateType {
|
||||
case initial
|
||||
case identityConfirmation
|
||||
case identityConfirmed
|
||||
case appLockSetup
|
||||
case analyticsPrompt
|
||||
case notificationPermissions
|
||||
case finished
|
||||
}
|
||||
|
||||
enum Event: EventType {
|
||||
case next
|
||||
}
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// periphery: ignore - used to store the coordinator to avoid deallocation
|
||||
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
|
||||
|
||||
init(userSession: UserSessionProtocol,
|
||||
appLockService: AppLockServiceProtocol,
|
||||
analyticsService: AnalyticsService,
|
||||
appSettings: AppSettings,
|
||||
notificationManager: NotificationManagerProtocol,
|
||||
navigationStackCoordinator: NavigationStackCoordinator,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
isNewLogin: Bool) {
|
||||
self.userSession = userSession
|
||||
self.appLockService = appLockService
|
||||
self.analyticsService = analyticsService
|
||||
self.appSettings = appSettings
|
||||
self.notificationManager = notificationManager
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.isNewLogin = isNewLogin
|
||||
|
||||
rootNavigationStackCoordinator = navigationStackCoordinator
|
||||
self.navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
stateMachine = .init(state: .initial)
|
||||
}
|
||||
|
||||
var shouldStart: Bool {
|
||||
guard stateMachine.state == .initial, !ProcessInfo.isRunningIntegrationTests else {
|
||||
return false
|
||||
}
|
||||
|
||||
return isNewLogin || requiresVerification || requiresAppLockSetup || requiresAnalyticsSetup || requiresNotificationsSetup
|
||||
}
|
||||
|
||||
func start() {
|
||||
guard shouldStart else {
|
||||
fatalError("This flow coordinator shouldn't have been started")
|
||||
}
|
||||
|
||||
configureStateMachine()
|
||||
|
||||
stateMachine.tryEvent(.next)
|
||||
|
||||
rootNavigationStackCoordinator.setFullScreenCoverCoordinator(navigationStackCoordinator, animated: !isNewLogin)
|
||||
}
|
||||
|
||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func clearRoute(animated: Bool) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var requiresVerification: Bool {
|
||||
// We want to make sure onboarding finishes but also every time the user becomes unverified (e.g. account reset)
|
||||
!appSettings.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified
|
||||
}
|
||||
|
||||
private var requiresAppLockSetup: Bool {
|
||||
appSettings.appLockIsMandatory && !appLockService.isEnabled
|
||||
}
|
||||
|
||||
private var requiresAnalyticsSetup: Bool {
|
||||
analyticsService.shouldShowAnalyticsPrompt
|
||||
}
|
||||
|
||||
private var requiresNotificationsSetup: Bool {
|
||||
!appSettings.hasRunNotificationPermissionsOnboarding
|
||||
}
|
||||
|
||||
private func configureStateMachine() {
|
||||
stateMachine.addRouteMapping { [weak self] _, fromState, _ in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch (fromState, requiresVerification, requiresAppLockSetup, requiresAnalyticsSetup, requiresNotificationsSetup) {
|
||||
case (.initial, true, _, _, _):
|
||||
return .identityConfirmation
|
||||
case (.initial, false, true, _, _):
|
||||
return .appLockSetup
|
||||
case (.initial, false, false, true, _):
|
||||
return .analyticsPrompt
|
||||
case (.initial, false, false, false, true):
|
||||
return .notificationPermissions
|
||||
|
||||
case (.identityConfirmation, _, _, _, _):
|
||||
return .identityConfirmed
|
||||
|
||||
case (.identityConfirmed, _, true, _, _):
|
||||
return .appLockSetup
|
||||
case (.identityConfirmed, _, false, true, _):
|
||||
return .analyticsPrompt
|
||||
case (.identityConfirmed, _, false, false, true):
|
||||
return .notificationPermissions
|
||||
case (.identityConfirmed, _, false, false, false):
|
||||
return .finished
|
||||
|
||||
case (.appLockSetup, _, _, true, _):
|
||||
return .analyticsPrompt
|
||||
case (.appLockSetup, _, _, false, true):
|
||||
return .notificationPermissions
|
||||
case (.appLockSetup, _, _, false, false):
|
||||
return .finished
|
||||
|
||||
case (.analyticsPrompt, _, _, _, true):
|
||||
return .notificationPermissions
|
||||
case (.analyticsPrompt, _, _, _, false):
|
||||
return .finished
|
||||
|
||||
case (.notificationPermissions, _, _, _, _):
|
||||
return .finished
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
stateMachine.addAnyHandler(.any => .any) { [weak self] context in
|
||||
guard let self else { return }
|
||||
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (_, _, .identityConfirmation):
|
||||
presentIdentityConfirmationScreen()
|
||||
case (_, _, .identityConfirmed):
|
||||
presentIdentityConfirmedScreen()
|
||||
case (_, _, .appLockSetup):
|
||||
presentAppLockSetupFlow()
|
||||
case (_, _, .analyticsPrompt):
|
||||
presentAnalyticsPromptScreen()
|
||||
case (_, _, .notificationPermissions):
|
||||
presentNotificationPermissionsScreen()
|
||||
case (_, _, .finished):
|
||||
rootNavigationStackCoordinator.setFullScreenCoverCoordinator(nil)
|
||||
default:
|
||||
fatalError("Unknown transition: \(context)")
|
||||
}
|
||||
}
|
||||
|
||||
stateMachine.addErrorHandler { context in
|
||||
fatalError("Unexpected transition: \(context)")
|
||||
}
|
||||
}
|
||||
|
||||
private func presentIdentityConfirmationScreen() {
|
||||
let parameters = IdentityConfirmationScreenCoordinatorParameters(userSession: userSession,
|
||||
appSettings: appSettings,
|
||||
userIndicatorController: userIndicatorController)
|
||||
|
||||
let coordinator = IdentityConfirmationScreenCoordinator(parameters: parameters)
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .otherDevice:
|
||||
Task {
|
||||
await self.presentSessionVerificationScreen()
|
||||
}
|
||||
case .recoveryKey:
|
||||
presentRecoveryKeyScreen()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentSessionVerificationScreen() async {
|
||||
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
}
|
||||
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
|
||||
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .done:
|
||||
appSettings.hasRunIdentityConfirmationOnboarding = true
|
||||
stateMachine.tryEvent(.next)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentRecoveryKeyScreen() {
|
||||
let parameters = SecureBackupRecoveryKeyScreenCoordinatorParameters(secureBackupController: userSession.clientProxy.secureBackupController,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
isModallyPresented: false)
|
||||
|
||||
let coordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .recoveryFixed:
|
||||
appSettings.hasRunIdentityConfirmationOnboarding = true
|
||||
stateMachine.tryEvent(.next)
|
||||
default:
|
||||
fatalError("Other flows shouldn't be possible")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentIdentityConfirmedScreen() {
|
||||
let coordinator = IdentityConfirmedScreenCoordinator(parameters: .init())
|
||||
coordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .done:
|
||||
stateMachine.tryEvent(.next)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentAppLockSetupFlow() {
|
||||
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding,
|
||||
appLockService: appLockService,
|
||||
navigationStackCoordinator: navigationStackCoordinator)
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .complete:
|
||||
appLockFlowCoordinator = nil
|
||||
stateMachine.tryEvent(.next)
|
||||
case .forceLogout:
|
||||
fatalError("The PIN creation flow should not fail.")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
appLockFlowCoordinator = coordinator
|
||||
coordinator.start()
|
||||
}
|
||||
|
||||
private func presentAnalyticsPromptScreen() {
|
||||
let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: appSettings.analyticsConfiguration.termsURL)
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .done:
|
||||
stateMachine.tryEvent(.next)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentNotificationPermissionsScreen() {
|
||||
let coordinator = NotificationPermissionsScreenCoordinator(parameters: .init(notificationManager: notificationManager))
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .done:
|
||||
appSettings.hasRunNotificationPermissionsOnboarding = true
|
||||
stateMachine.tryEvent(.next)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentCoordinator(_ coordinator: CoordinatorProtocol) {
|
||||
if navigationStackCoordinator.rootCoordinator == nil {
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
} else {
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,8 +434,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||
|
||||
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
secureBackupController: userSession.clientProxy.secureBackupController)
|
||||
timelineItemFactory: timelineItemFactory)
|
||||
self.timelineController = timelineController
|
||||
|
||||
analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace)
|
||||
@@ -918,8 +917,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||
|
||||
let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
secureBackupController: userSession.clientProxy.secureBackupController)
|
||||
timelineItemFactory: timelineItemFactory)
|
||||
|
||||
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||
pollInteractionHandler: PollInteractionHandler(analyticsService: analytics, roomProxy: roomProxy),
|
||||
|
||||
@@ -130,10 +130,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
presentLegalInformationScreen()
|
||||
case .blockedUsers:
|
||||
presentBlockedUsersScreen()
|
||||
case .sessionVerification:
|
||||
Task {
|
||||
await self.presentSessionVerificationScreen()
|
||||
}
|
||||
case .accountSessions:
|
||||
presentAccountSessionsListURL()
|
||||
case .notifications:
|
||||
@@ -212,35 +208,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
userIndicatorController: parameters.userIndicatorController))
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
|
||||
private func presentSessionVerificationScreen() async {
|
||||
guard case let .success(sessionVerificationController) = await parameters.userSession.clientProxy.sessionVerificationControllerProxy() else {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
}
|
||||
|
||||
let verificationParameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||
recoveryState: parameters.userSession.sessionSecurityStatePublisher.value.recoveryState)
|
||||
let coordinator = SessionVerificationScreenCoordinator(parameters: verificationParameters)
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .recoveryKey:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .done:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.setSheetCoordinator(coordinator) { [weak self] in
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentNotificationSettings() {
|
||||
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
userSession: parameters.userSession,
|
||||
|
||||
@@ -31,7 +31,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let windowManager: WindowManagerProtocol
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
|
||||
|
||||
private let stateMachine: UserSessionFlowCoordinatorStateMachine
|
||||
|
||||
@@ -39,6 +38,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private let settingsFlowCoordinator: SettingsFlowCoordinator
|
||||
|
||||
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
|
||||
|
||||
// periphery:ignore - retaining purpose
|
||||
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
||||
|
||||
@@ -52,7 +53,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private let selectedRoomSubject = CurrentValueSubject<String?, Never>(nil)
|
||||
|
||||
var actions: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
|
||||
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@@ -63,7 +65,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
bugReportService: BugReportServiceProtocol,
|
||||
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService) {
|
||||
analytics: AnalyticsService,
|
||||
notificationManager: NotificationManagerProtocol,
|
||||
isNewLogin: Bool) {
|
||||
stateMachine = UserSessionFlowCoordinatorStateMachine()
|
||||
self.userSession = userSession
|
||||
self.navigationRootCoordinator = navigationRootCoordinator
|
||||
@@ -98,8 +102,29 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController))
|
||||
|
||||
onboardingFlowCoordinator = OnboardingFlowCoordinator(userSession: userSession,
|
||||
appLockService: appLockService,
|
||||
analyticsService: analytics,
|
||||
appSettings: appSettings,
|
||||
notificationManager: notificationManager,
|
||||
navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
isNewLogin: isNewLogin)
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
userSession.sessionSecurityStatePublisher
|
||||
.map(\.verificationState)
|
||||
.filter { $0 != .unknown }
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
|
||||
attemptStartingOnboarding()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomFlowCoordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -208,11 +233,18 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
func clearRoute(animated: Bool) {
|
||||
fatalError("not necessary as of right now")
|
||||
roomFlowCoordinator.clearRoute(animated: animated)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
func attemptStartingOnboarding() {
|
||||
if onboardingFlowCoordinator.shouldStart {
|
||||
clearRoute(animated: false)
|
||||
onboardingFlowCoordinator.start()
|
||||
}
|
||||
}
|
||||
|
||||
private func clearPresentedSheets(animated: Bool, completion: @escaping () -> Void) {
|
||||
if navigationSplitCoordinator.sheetCoordinator == nil {
|
||||
completion()
|
||||
@@ -234,6 +266,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (.initial, .start, .roomList):
|
||||
presentHomeScreen()
|
||||
attemptStartingOnboarding()
|
||||
|
||||
case(.roomList, .selectRoom, .roomList):
|
||||
break
|
||||
@@ -244,13 +277,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
break
|
||||
case (.invitesScreen, .deselectRoom, .invitesScreen):
|
||||
break
|
||||
|
||||
case (.roomList, .showSessionVerificationScreen, .sessionVerificationScreen):
|
||||
Task {
|
||||
await self.presentSessionVerification(animated: animated)
|
||||
}
|
||||
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .roomList):
|
||||
break
|
||||
|
||||
case (.roomList, .showSettingsScreen, .settingsScreen):
|
||||
break
|
||||
@@ -334,8 +360,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
|
||||
case .presentFeedbackScreen:
|
||||
stateMachine.processEvent(.feedbackScreen)
|
||||
case .presentSessionVerificationScreen:
|
||||
stateMachine.processEvent(.showSessionVerificationScreen)
|
||||
case .presentSecureBackupSettings:
|
||||
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .presentStartChatScreen:
|
||||
@@ -400,36 +424,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
presentSecureBackupLogoutConfirmationScreen()
|
||||
}
|
||||
|
||||
// MARK: Session verification
|
||||
|
||||
private func presentSessionVerification(animated: Bool) async {
|
||||
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
}
|
||||
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||
recoveryState: userSession.sessionSecurityStatePublisher.value.recoveryState)
|
||||
|
||||
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .recoveryKey:
|
||||
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .done:
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationSplitCoordinator.setSheetCoordinator(coordinator, animated: animated) { [weak self] in
|
||||
self?.stateMachine.processEvent(.dismissedSessionVerificationScreen)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Start Chat
|
||||
|
||||
private func presentStartChat(animated: Bool) {
|
||||
|
||||
@@ -26,9 +26,6 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
/// Showing the home screen. The `selectedRoomID` represents the timeline shown on the detail panel (if any)
|
||||
case roomList(selectedRoomID: String?)
|
||||
|
||||
/// Showing the session verification flows
|
||||
case sessionVerificationScreen(selectedRoomID: String?)
|
||||
|
||||
/// Showing the session verification flows
|
||||
case feedbackScreen(selectedRoomID: String?)
|
||||
|
||||
@@ -70,11 +67,6 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
/// The feedback screen has been dismissed
|
||||
case dismissedFeedbackScreen
|
||||
|
||||
/// Request the start of the session verification flow
|
||||
case showSessionVerificationScreen
|
||||
/// Session verification has finished
|
||||
case dismissedSessionVerificationScreen
|
||||
|
||||
/// Request the start of the start chat flow
|
||||
case showStartChatScreen
|
||||
/// Start chat has been dismissed
|
||||
@@ -126,11 +118,6 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
case (.feedbackScreen(let selectedRoomID), .dismissedFeedbackScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showSessionVerificationScreen):
|
||||
return .sessionVerificationScreen(selectedRoomID: selectedRoomID)
|
||||
case (.sessionVerificationScreen(let selectedRoomID), .dismissedSessionVerificationScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showStartChatScreen):
|
||||
return .startChatScreen(selectedRoomID: selectedRoomID)
|
||||
case (.startChatScreen(let selectedRoomID), .dismissedStartChatScreen):
|
||||
|
||||
Reference in New Issue
Block a user