Files
letro-ios/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenStateMachine.swift
Stefan Ceriu d5555a9b23 Fix session verification SAS flow not automatically starting
With #5116 removing the `verificationRequestAccepted` view state and its
`.startSasVerification` button action we now need another way of starting the SAS
flows whenever the verification requests gets accepted. As such a state machine
transition from `acceptingVerificationRequest` to `verificationRequestAccepted`
will now automatically call `startSasVerification` on the SessionVerificationProxy.
2026-04-20 18:19:27 +03:00

148 lines
5.8 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import SwiftState
class SessionVerificationScreenStateMachine {
/// States the SessionVerificationViewModel can find itself in
enum State: StateType {
/// The initial state, before verification started
case initial
/// Accepting the remote verification request
case acceptingVerificationRequest
/// Waiting for verification acceptance
case requestingVerification
/// Verification request accepted. Waiting for start
case verificationRequestAccepted
/// Waiting for SaS verification start
case startingSasVerification
/// A SaS verification flow has been started
case sasVerificationStarted
/// Verification accepted and emojis received
case showingChallenge(emojis: [SessionVerificationEmoji])
/// Emojis match locally
case acceptingChallenge(emojis: [SessionVerificationEmoji])
/// Emojis do not match locally
case decliningChallenge(emojis: [SessionVerificationEmoji])
/// Verification successful
case verified
/// User requested verification cancellation
case cancelling
/// The verification has been cancelled, remotely or locally
case cancelled
}
/// Events that can be triggered on the SessionVerification state machine
enum Event: EventType {
/// Accept the remote verification request
case acceptVerificationRequest
/// Request verification
case requestVerification
/// The current verification request has been accepted
case didAcceptVerificationRequest
/// Started a SaS verification flow
case didStartSasVerification
/// Has received emojis
case didReceiveChallenge(emojis: [SessionVerificationEmoji])
/// Emojis match
case acceptChallenge
/// Emojis do not match
case declineChallenge
/// Remote accepted challenge
case didAcceptChallenge
/// Request cancellation
case cancel
/// Verification cancelled
case didCancel
/// Request failed
case didFail
/// Restart the verification flow
case restart
}
private let stateMachine: StateMachine<State, Event>
var state: State {
stateMachine.state
}
init(state: State) {
stateMachine = StateMachine(state: state)
configure()
}
private func configure() {
stateMachine.addRoutes(event: .acceptVerificationRequest, transitions: [.initial => .acceptingVerificationRequest])
stateMachine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.acceptingVerificationRequest => .verificationRequestAccepted,
.requestingVerification => .verificationRequestAccepted])
stateMachine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial,
.acceptingVerificationRequest => .initial])
stateMachine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
// Transitions with associated values need to be handled through `addRouteMapping`
stateMachine.addRouteMapping { event, fromState, _ in
switch (fromState, event) {
case (_, .didStartSasVerification):
return .sasVerificationStarted
case (.sasVerificationStarted, .didReceiveChallenge(let emojis)):
return .showingChallenge(emojis: emojis)
case (.showingChallenge(let emojis), .acceptChallenge):
return .acceptingChallenge(emojis: emojis)
case (.acceptingChallenge(let emojis), .didFail):
return .showingChallenge(emojis: emojis)
case (.acceptingChallenge, .didAcceptChallenge):
return .verified
case (.showingChallenge(let emojis), .declineChallenge):
return .decliningChallenge(emojis: emojis)
case (.decliningChallenge(let emojis), .didFail):
return .showingChallenge(emojis: emojis)
case (_, .cancel):
return .cancelling
case (_, .didCancel):
return .cancelled
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
/// It will either invoke the `transitionHandler` or the `errorHandler` depending on its current state
func processEvent(_ event: Event) {
stateMachine.tryEvent(event)
}
/// Registers a callback for processing state machine transitions
func addTransitionHandler(_ handler: @escaping StateMachine<State, Event>.Handler) {
stateMachine.addAnyHandler(.any => .any, handler: handler)
}
/// Registers a callback for processing state machine errors
func addErrorHandler(_ handler: @escaping StateMachine<State, Event>.Handler) {
stateMachine.addErrorHandler(handler: handler)
}
}