// // 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 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.Handler) { stateMachine.addAnyHandler(.any => .any, handler: handler) } /// Registers a callback for processing state machine errors func addErrorHandler(_ handler: @escaping StateMachine.Handler) { stateMachine.addErrorHandler(handler: handler) } }