Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User session state machine crashes #281

Merged
merged 2 commits into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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