mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Fix state machine crashes when backgrounding the app before the user session is setup
|
||||||
Reference in New Issue
Block a user