diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 53719434bc..0ba64460f6 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -23,10 +23,10 @@ struct HomeScreenCoordinatorParameters { } enum HomeScreenCoordinatorAction { - case presentRoom(roomIdentifier: String) - case presentSettings - case presentBugReport - case verifySession + case presentRoomScreen(roomIdentifier: String) + case presentSettingsScreen + case presentFeedbackScreen + case presentSessionVerificationScreen case signOut } @@ -63,11 +63,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable { switch action { case .selectRoom(let roomIdentifier): - self.callback?(.presentRoom(roomIdentifier: roomIdentifier)) + self.callback?(.presentRoomScreen(roomIdentifier: roomIdentifier)) case .userMenu(let action): self.processUserMenuAction(action) case .verifySession: - self.callback?(.verifySession) + self.callback?(.presentSessionVerificationScreen) } } } @@ -87,11 +87,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable { private func processUserMenuAction(_ action: HomeScreenViewUserMenuAction) { switch action { case .settings: - callback?(.presentSettings) + callback?(.presentSettingsScreen) case .inviteFriends: presentInviteFriends() case .feedback: - callback?(.presentBugReport) + callback?(.presentFeedbackScreen) case .signOut: callback?(.signOut) } diff --git a/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift b/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift index e0d36c42fb..3e2b97af0f 100644 --- a/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift @@ -72,6 +72,12 @@ class UserSessionFlowCoordinator: Coordinator { self.presentSettingsScreen() case (.settingsScreen, .dismissedSettingsScreen, .homeScreen): self.dismissSettingsScreen() + + case (.homeScreen, .feedbackScreen, .feedbackScreen): + self.presentFeedbackScreen() + case (.feedbackScreen, .dismissedFeedbackScreen, .homeScreen): + self.dismissFeedbackScreen() + case (_, .resignActive, .suspended): self.pause() case (_, .becomeActive, _): @@ -109,13 +115,13 @@ class UserSessionFlowCoordinator: Coordinator { guard let self else { return } switch action { - case .presentRoom(let roomIdentifier): + case .presentRoomScreen(let roomIdentifier): self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier)) - case .presentSettings: + case .presentSettingsScreen: self.stateMachine.processEvent(.showSettingsScreen) - case .presentBugReport: - self.presentBugReportScreen() - case .verifySession: + case .presentFeedbackScreen: + self.stateMachine.processEvent(.feedbackScreen) + case .presentSessionVerificationScreen: self.stateMachine.processEvent(.showSessionVerificationScreen) case .signOut: self.callback?(.signOut) @@ -254,20 +260,18 @@ class UserSessionFlowCoordinator: Coordinator { alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel)) alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in - self?.presentBugReportScreen() + self?.stateMachine.processEvent(.feedbackScreen) }) navigationRouter.present(alert, animated: true) } - private func presentBugReportScreen(for image: UIImage? = nil) { + private func presentFeedbackScreen(for image: UIImage? = nil) { let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService, screenshot: image) let coordinator = BugReportCoordinator(parameters: parameters) - coordinator.completion = { [weak self, weak coordinator] in - guard let self, let coordinator = coordinator else { return } - self.navigationRouter.dismissModule(animated: true) - self.remove(childCoordinator: coordinator) + coordinator.completion = { [weak self] in + self?.stateMachine.processEvent(.dismissedFeedbackScreen) } add(childCoordinator: coordinator) @@ -275,21 +279,23 @@ class UserSessionFlowCoordinator: Coordinator { let navController = ElementNavigationController(rootViewController: coordinator.toPresentable()) navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, - action: #selector(dismissBugReportScreen)) + action: #selector(handleFeedbackScreenCancellation)) navController.isModalInPresentation = true navigationRouter.present(navController, animated: true) } @objc - private func dismissBugReportScreen() { - MXLog.debug("dismissBugReportScreen") - - guard let bugReportCoordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else { + private func handleFeedbackScreenCancellation() { + stateMachine.processEvent(.dismissedFeedbackScreen) + } + + private func dismissFeedbackScreen() { + guard let coordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else { return } - + navigationRouter.dismissModule() - remove(childCoordinator: bugReportCoordinator) + remove(childCoordinator: coordinator) } // MARK: - Application State diff --git a/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift b/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift index 6d01baf96a..c2e46f0def 100644 --- a/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/UserSession/UserSessionFlowCoordinatorStateMachine.swift @@ -33,6 +33,9 @@ class UserSessionFlowCoordinatorStateMachine { /// Showing the session verification flows case sessionVerificationScreen + /// Showing the session verification flows + case feedbackScreen + /// Showing the settings screen case settingsScreen @@ -51,15 +54,20 @@ class UserSessionFlowCoordinatorStateMachine { /// The room screen has been dismissed case dismissedRoomScreen - /// Request the start of the session verification flow - case showSessionVerificationScreen - /// Session verification has finished - case dismissedSessionVerificationScreen - /// Request presentation of the settings screen case showSettingsScreen /// The settings screen has been dismissed case dismissedSettingsScreen + + /// Request presentation of the feedback screen + case feedbackScreen + /// The feedback screen has been dismissed + case dismissedFeedbackScreen + + /// Request the start of the session verification flow + case showSessionVerificationScreen + /// Session verification has finished + case dismissedSessionVerificationScreen /// Application goes into inactive state case resignActive @@ -68,13 +76,14 @@ class UserSessionFlowCoordinatorStateMachine { } private let stateMachine: StateMachine - private var stateBeforeSuspension: State = .initial + private var stateBeforeSuspension: State? init() { stateMachine = StateMachine(state: .initial) configure() } + // swiftlint:disable:next cyclomatic_complexity private func configure() { stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen]) @@ -84,23 +93,46 @@ class UserSessionFlowCoordinatorStateMachine { return .roomScreen(roomId: roomId) case (.dismissedRoomScreen, .roomScreen): return .homeScreen - case (.resignActive, _): - self.stateBeforeSuspension = fromState - return .suspended - case (.becomeActive, .suspended): - return self.stateBeforeSuspension + case (.showSettingsScreen, .homeScreen): return .settingsScreen case (.dismissedSettingsScreen, .settingsScreen): return .homeScreen + + case (.feedbackScreen, .homeScreen): + return .feedbackScreen + case (.dismissedFeedbackScreen, .feedbackScreen): + return .homeScreen + case (.showSessionVerificationScreen, .homeScreen): return .sessionVerificationScreen case (.dismissedSessionVerificationScreen, .sessionVerificationScreen): return .homeScreen + + case (.resignActive, _): + self.stateBeforeSuspension = fromState + return .suspended + case (.becomeActive, _): + // Cannot become active if not previously suspended + // Happens when the app is backgrounded before the session is setup + guard let previousState = self.stateBeforeSuspension else { + return self.stateMachine.state + } + + return previousState + default: return nil } } + + addTransitionHandler { context in + 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)`") + } + } } /// Attempt to move the state machine to another state through an event diff --git a/changelog.d/277.bugfix b/changelog.d/277.bugfix new file mode 100644 index 0000000000..3aef05cccb --- /dev/null +++ b/changelog.d/277.bugfix @@ -0,0 +1 @@ +Fix state machine crashes when backgrounding the app before the user session is setup \ No newline at end of file