User session state machine crashes (#281)

* Fix #277 - UserSession state machine crashes on foregrounding and add the bug report screen to it
* Rename home screen actions
This commit is contained in:
Stefan Ceriu
2022-11-02 12:44:51 +02:00
committed by GitHub
parent d398b96abd
commit b270b8a30e
4 changed files with 76 additions and 37 deletions
@@ -23,10 +23,10 @@ struct HomeScreenCoordinatorParameters {
} }
enum HomeScreenCoordinatorAction { enum HomeScreenCoordinatorAction {
case presentRoom(roomIdentifier: String) case presentRoomScreen(roomIdentifier: String)
case presentSettings case presentSettingsScreen
case presentBugReport case presentFeedbackScreen
case verifySession case presentSessionVerificationScreen
case signOut case signOut
} }
@@ -63,11 +63,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
switch action { switch action {
case .selectRoom(let roomIdentifier): case .selectRoom(let roomIdentifier):
self.callback?(.presentRoom(roomIdentifier: roomIdentifier)) self.callback?(.presentRoomScreen(roomIdentifier: roomIdentifier))
case .userMenu(let action): case .userMenu(let action):
self.processUserMenuAction(action) self.processUserMenuAction(action)
case .verifySession: case .verifySession:
self.callback?(.verifySession) self.callback?(.presentSessionVerificationScreen)
} }
} }
} }
@@ -87,11 +87,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
private func processUserMenuAction(_ action: HomeScreenViewUserMenuAction) { private func processUserMenuAction(_ action: HomeScreenViewUserMenuAction) {
switch action { switch action {
case .settings: case .settings:
callback?(.presentSettings) callback?(.presentSettingsScreen)
case .inviteFriends: case .inviteFriends:
presentInviteFriends() presentInviteFriends()
case .feedback: case .feedback:
callback?(.presentBugReport) callback?(.presentFeedbackScreen)
case .signOut: case .signOut:
callback?(.signOut) callback?(.signOut)
} }
@@ -72,6 +72,12 @@ class UserSessionFlowCoordinator: Coordinator {
self.presentSettingsScreen() self.presentSettingsScreen()
case (.settingsScreen, .dismissedSettingsScreen, .homeScreen): case (.settingsScreen, .dismissedSettingsScreen, .homeScreen):
self.dismissSettingsScreen() self.dismissSettingsScreen()
case (.homeScreen, .feedbackScreen, .feedbackScreen):
self.presentFeedbackScreen()
case (.feedbackScreen, .dismissedFeedbackScreen, .homeScreen):
self.dismissFeedbackScreen()
case (_, .resignActive, .suspended): case (_, .resignActive, .suspended):
self.pause() self.pause()
case (_, .becomeActive, _): case (_, .becomeActive, _):
@@ -109,13 +115,13 @@ class UserSessionFlowCoordinator: Coordinator {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .presentRoom(let roomIdentifier): case .presentRoomScreen(let roomIdentifier):
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier)) self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
case .presentSettings: case .presentSettingsScreen:
self.stateMachine.processEvent(.showSettingsScreen) self.stateMachine.processEvent(.showSettingsScreen)
case .presentBugReport: case .presentFeedbackScreen:
self.presentBugReportScreen() self.stateMachine.processEvent(.feedbackScreen)
case .verifySession: case .presentSessionVerificationScreen:
self.stateMachine.processEvent(.showSessionVerificationScreen) self.stateMachine.processEvent(.showSessionVerificationScreen)
case .signOut: case .signOut:
self.callback?(.signOut) self.callback?(.signOut)
@@ -254,20 +260,18 @@ class UserSessionFlowCoordinator: Coordinator {
alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel)) alert.addAction(UIAlertAction(title: ElementL10n.no, style: .cancel))
alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in alert.addAction(UIAlertAction(title: ElementL10n.yes, style: .default) { [weak self] _ in
self?.presentBugReportScreen() self?.stateMachine.processEvent(.feedbackScreen)
}) })
navigationRouter.present(alert, animated: true) navigationRouter.present(alert, animated: true)
} }
private func presentBugReportScreen(for image: UIImage? = nil) { private func presentFeedbackScreen(for image: UIImage? = nil) {
let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService, let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService,
screenshot: image) screenshot: image)
let coordinator = BugReportCoordinator(parameters: parameters) let coordinator = BugReportCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] in coordinator.completion = { [weak self] in
guard let self, let coordinator = coordinator else { return } self?.stateMachine.processEvent(.dismissedFeedbackScreen)
self.navigationRouter.dismissModule(animated: true)
self.remove(childCoordinator: coordinator)
} }
add(childCoordinator: coordinator) add(childCoordinator: coordinator)
@@ -275,21 +279,23 @@ class UserSessionFlowCoordinator: Coordinator {
let navController = ElementNavigationController(rootViewController: coordinator.toPresentable()) let navController = ElementNavigationController(rootViewController: coordinator.toPresentable())
navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self, target: self,
action: #selector(dismissBugReportScreen)) action: #selector(handleFeedbackScreenCancellation))
navController.isModalInPresentation = true navController.isModalInPresentation = true
navigationRouter.present(navController, animated: true) navigationRouter.present(navController, animated: true)
} }
@objc @objc
private func dismissBugReportScreen() { private func handleFeedbackScreenCancellation() {
MXLog.debug("dismissBugReportScreen") stateMachine.processEvent(.dismissedFeedbackScreen)
}
guard let bugReportCoordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else {
private func dismissFeedbackScreen() {
guard let coordinator = childCoordinators.first(where: { $0 is BugReportCoordinator }) else {
return return
} }
navigationRouter.dismissModule() navigationRouter.dismissModule()
remove(childCoordinator: bugReportCoordinator) remove(childCoordinator: coordinator)
} }
// MARK: - Application State // MARK: - Application State
@@ -33,6 +33,9 @@ class UserSessionFlowCoordinatorStateMachine {
/// Showing the session verification flows /// Showing the session verification flows
case sessionVerificationScreen case sessionVerificationScreen
/// Showing the session verification flows
case feedbackScreen
/// Showing the settings screen /// Showing the settings screen
case settingsScreen case settingsScreen
@@ -51,15 +54,20 @@ class UserSessionFlowCoordinatorStateMachine {
/// The room screen has been dismissed /// The room screen has been dismissed
case dismissedRoomScreen case dismissedRoomScreen
/// Request the start of the session verification flow
case showSessionVerificationScreen
/// Session verification has finished
case dismissedSessionVerificationScreen
/// Request presentation of the settings screen /// Request presentation of the settings screen
case showSettingsScreen case showSettingsScreen
/// The settings screen has been dismissed /// The settings screen has been dismissed
case dismissedSettingsScreen 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 /// Application goes into inactive state
case resignActive case resignActive
@@ -68,13 +76,14 @@ class UserSessionFlowCoordinatorStateMachine {
} }
private let stateMachine: StateMachine<State, Event> private let stateMachine: StateMachine<State, Event>
private var stateBeforeSuspension: State = .initial private var stateBeforeSuspension: State?
init() { init() {
stateMachine = StateMachine(state: .initial) stateMachine = StateMachine(state: .initial)
configure() configure()
} }
// swiftlint:disable:next cyclomatic_complexity
private func configure() { private func configure() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen]) stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen])
@@ -84,23 +93,46 @@ class UserSessionFlowCoordinatorStateMachine {
return .roomScreen(roomId: roomId) return .roomScreen(roomId: roomId)
case (.dismissedRoomScreen, .roomScreen): case (.dismissedRoomScreen, .roomScreen):
return .homeScreen return .homeScreen
case (.resignActive, _):
self.stateBeforeSuspension = fromState
return .suspended
case (.becomeActive, .suspended):
return self.stateBeforeSuspension
case (.showSettingsScreen, .homeScreen): case (.showSettingsScreen, .homeScreen):
return .settingsScreen return .settingsScreen
case (.dismissedSettingsScreen, .settingsScreen): case (.dismissedSettingsScreen, .settingsScreen):
return .homeScreen return .homeScreen
case (.feedbackScreen, .homeScreen):
return .feedbackScreen
case (.dismissedFeedbackScreen, .feedbackScreen):
return .homeScreen
case (.showSessionVerificationScreen, .homeScreen): case (.showSessionVerificationScreen, .homeScreen):
return .sessionVerificationScreen return .sessionVerificationScreen
case (.dismissedSessionVerificationScreen, .sessionVerificationScreen): case (.dismissedSessionVerificationScreen, .sessionVerificationScreen):
return .homeScreen 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: default:
return nil 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 /// Attempt to move the state machine to another state through an event
+1
View File
@@ -0,0 +1 @@
Fix state machine crashes when backgrounding the app before the user session is setup