Skip to content

Commit

Permalink
User session state machine crashes (#281)
Browse files Browse the repository at this point in the history
* Fix #277 - UserSession state machine crashes on foregrounding and add the bug report screen to it
* Rename home screen actions
  • Loading branch information
stefanceriu authored Nov 2, 2022
1 parent d398b96 commit b270b8a
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 37 deletions.
16 changes: 8 additions & 8 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}
}
}
Expand All @@ -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)
}
Expand Down
42 changes: 24 additions & 18 deletions ElementX/Sources/UserSession/UserSessionFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, _):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -254,42 +260,42 @@ 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)
coordinator.start()
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -68,13 +76,14 @@ class UserSessionFlowCoordinatorStateMachine {
}

private let stateMachine: StateMachine<State, Event>
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])

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions changelog.d/277.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix state machine crashes when backgrounding the app before the user session is setup

0 comments on commit b270b8a

Please sign in to comment.