mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Move session related responsibilities from ChatsFlowFlowCoordinator to UserSessionFlowCoordinator.
Specifically: onboarding, session verification and logout.
This commit is contained in:
@@ -17,6 +17,8 @@ import SwiftUI
|
|||||||
let selectedIcon: KeyPath<CompoundIcons, Image>
|
let selectedIcon: KeyPath<CompoundIcons, Image>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Tabs
|
||||||
|
|
||||||
fileprivate struct TabModule: Identifiable {
|
fileprivate struct TabModule: Identifiable {
|
||||||
let module: NavigationModule
|
let module: NavigationModule
|
||||||
let title: String
|
let title: String
|
||||||
@@ -58,8 +60,108 @@ import SwiftUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Sheets
|
||||||
|
|
||||||
|
fileprivate var sheetModule: NavigationModule? {
|
||||||
|
didSet {
|
||||||
|
if let oldValue {
|
||||||
|
logPresentationChange("Remove sheet", oldValue)
|
||||||
|
oldValue.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let sheetModule {
|
||||||
|
logPresentationChange("Set sheet", sheetModule)
|
||||||
|
sheetModule.coordinator?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var presentationDetents: Set<PresentationDetent> = []
|
||||||
|
|
||||||
|
/// The currently presented sheet coordinator.
|
||||||
|
var sheetCoordinator: (any CoordinatorProtocol)? {
|
||||||
|
sheetModule?.coordinator
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Present a sheet on top of the stack. If this NavigationStackCoordinator is embedded within a NavigationSplitCoordinator
|
||||||
|
/// then the presentation will be proxied to the split
|
||||||
|
/// - Parameters:
|
||||||
|
/// - coordinator: the coordinator to display
|
||||||
|
/// - animated: whether to animate the transition or not. Default is true
|
||||||
|
|
||||||
|
/// - dismissalCallback: called when the sheet has been dismissed, programatically or otherwise
|
||||||
|
func setSheetCoordinator(_ coordinator: (any CoordinatorProtocol)?, animated: Bool = true, dismissalCallback: (() -> Void)? = nil) {
|
||||||
|
guard let coordinator else {
|
||||||
|
sheetModule = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sheetModule?.coordinator === coordinator {
|
||||||
|
fatalError("Cannot use the same coordinator more than once")
|
||||||
|
}
|
||||||
|
|
||||||
|
var transaction = Transaction()
|
||||||
|
transaction.disablesAnimations = !animated
|
||||||
|
|
||||||
|
withTransaction(transaction) {
|
||||||
|
sheetModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Full Screen Cover
|
||||||
|
|
||||||
|
fileprivate var fullScreenCoverModule: NavigationModule? {
|
||||||
|
didSet {
|
||||||
|
if let oldValue {
|
||||||
|
logPresentationChange("Remove fullscreen cover", oldValue)
|
||||||
|
oldValue.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let fullScreenCoverModule {
|
||||||
|
logPresentationChange("Set fullscreen cover", fullScreenCoverModule)
|
||||||
|
fullScreenCoverModule.coordinator?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The currently presented fullscreen cover coordinator
|
||||||
|
/// Fullscreen covers will be presented through the NavigationSplitCoordinator if provided
|
||||||
|
var fullScreenCoverCoordinator: (any CoordinatorProtocol)? {
|
||||||
|
fullScreenCoverModule?.coordinator
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Present a fullscreen cover on top of the stack. If this NavigationStackCoordinator is embedded within a NavigationSplitCoordinator
|
||||||
|
/// then the presentation will be proxied to the split
|
||||||
|
/// - Parameters:
|
||||||
|
/// - coordinator: the coordinator to display
|
||||||
|
/// - animated: whether to animate the transition or not. Default is true
|
||||||
|
/// - dismissalCallback: called when the fullscreen cover has been dismissed, programatically or otherwise
|
||||||
|
func setFullScreenCoverCoordinator(_ coordinator: (any CoordinatorProtocol)?, animated: Bool = true, dismissalCallback: (() -> Void)? = nil) {
|
||||||
|
guard let coordinator else {
|
||||||
|
fullScreenCoverModule = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullScreenCoverModule?.coordinator === coordinator {
|
||||||
|
fatalError("Cannot use the same coordinator more than once")
|
||||||
|
}
|
||||||
|
|
||||||
|
var transaction = Transaction()
|
||||||
|
transaction.disablesAnimations = !animated
|
||||||
|
|
||||||
|
withTransaction(transaction) {
|
||||||
|
fullScreenCoverModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - CoordinatorProtocol
|
// MARK: - CoordinatorProtocol
|
||||||
|
|
||||||
|
/// No idea if this is particuarly needed for the TabView but we do this for the NavigationStackCoordinator and NavigationSplitCoordinator so it
|
||||||
|
/// doesn't seem to harm to also do it here.
|
||||||
|
func stop() {
|
||||||
|
tabModules.forEach { $0.module.tearDown() }
|
||||||
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(NavigationTabCoordinatorView(navigationTabCoordinator: self))
|
AnyView(NavigationTabCoordinatorView(navigationTabCoordinator: self))
|
||||||
}
|
}
|
||||||
@@ -99,5 +201,13 @@ private struct NavigationTabCoordinatorView: View {
|
|||||||
.id(module.id)
|
.id(module.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(item: $navigationTabCoordinator.sheetModule) { module in
|
||||||
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
|
}
|
||||||
|
.fullScreenCover(item: $navigationTabCoordinator.fullScreenCoverModule) { module in
|
||||||
|
module.coordinator?.toPresentable()
|
||||||
|
.id(module.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import SwiftUI
|
|||||||
|
|
||||||
enum ChatsFlowCoordinatorAction {
|
enum ChatsFlowCoordinatorAction {
|
||||||
case logout
|
case logout
|
||||||
|
case sessionVerification(SessionVerificationScreenFlow)
|
||||||
case clearCache
|
case clearCache
|
||||||
/// Logout without a confirmation. The user forgot their PIN.
|
/// Logout and disable App Lock without any confirmation. The user forgot their PIN.
|
||||||
case forceLogout
|
case forceLogout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +38,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
private let settingsFlowCoordinator: SettingsFlowCoordinator
|
private let settingsFlowCoordinator: SettingsFlowCoordinator
|
||||||
|
|
||||||
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
|
|
||||||
|
|
||||||
// periphery:ignore - retaining purpose
|
// periphery:ignore - retaining purpose
|
||||||
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
||||||
|
|
||||||
@@ -103,16 +102,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
analytics: analytics))
|
analytics: analytics))
|
||||||
|
|
||||||
onboardingFlowCoordinator = OnboardingFlowCoordinator(userSession: userSession,
|
|
||||||
appLockService: appLockService,
|
|
||||||
analyticsService: analytics,
|
|
||||||
appSettings: appSettings,
|
|
||||||
notificationManager: notificationManager,
|
|
||||||
navigationStackCoordinator: detailNavigationStackCoordinator,
|
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
|
||||||
windowManager: appMediator.windowManager,
|
|
||||||
isNewLogin: isNewLogin)
|
|
||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
|
|
||||||
setupObservers()
|
setupObservers()
|
||||||
@@ -216,15 +205,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func attemptStartingOnboarding() {
|
|
||||||
MXLog.info("Attempting to start onboarding")
|
|
||||||
|
|
||||||
if onboardingFlowCoordinator.shouldStart {
|
|
||||||
clearRoute(animated: false)
|
|
||||||
onboardingFlowCoordinator.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func clearPresentedSheets(animated: Bool) async {
|
private func clearPresentedSheets(animated: Bool) async {
|
||||||
if navigationSplitCoordinator.sheetCoordinator == nil {
|
if navigationSplitCoordinator.sheetCoordinator == nil {
|
||||||
return
|
return
|
||||||
@@ -243,7 +223,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch (context.fromState, context.event, context.toState) {
|
switch (context.fromState, context.event, context.toState) {
|
||||||
case (.initial, .start, .roomList):
|
case (.initial, .start, .roomList):
|
||||||
presentHomeScreen()
|
presentHomeScreen()
|
||||||
attemptStartingOnboarding()
|
|
||||||
case(.roomList(let roomListSelectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
|
case(.roomList(let roomListSelectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
|
||||||
if roomListSelectedRoomID == roomID,
|
if roomListSelectedRoomID == roomID,
|
||||||
!entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading.
|
!entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading.
|
||||||
@@ -291,11 +270,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.startChatScreen, .dismissedStartChatScreen, .roomList):
|
case (.startChatScreen, .dismissedStartChatScreen, .roomList):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.roomList, .showLogoutConfirmationScreen, .logoutConfirmationScreen):
|
|
||||||
presentSecureBackupLogoutConfirmationScreen()
|
|
||||||
case (.logoutConfirmationScreen, .dismissedLogoutConfirmationScreen, .roomList):
|
|
||||||
break
|
|
||||||
|
|
||||||
case (.roomList, .showRoomDirectorySearchScreen, .roomDirectorySearchScreen):
|
case (.roomList, .showRoomDirectorySearchScreen, .roomDirectorySearchScreen):
|
||||||
presentRoomDirectorySearch()
|
presentRoomDirectorySearch()
|
||||||
case (.roomDirectorySearchScreen, .dismissedRoomDirectorySearchScreen, .roomList):
|
case (.roomDirectorySearchScreen, .dismissedRoomDirectorySearchScreen, .roomList):
|
||||||
@@ -352,19 +326,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupObservers() {
|
private func setupObservers() {
|
||||||
userSession.sessionSecurityStatePublisher
|
|
||||||
.map(\.verificationState)
|
|
||||||
.filter { $0 != .unknown }
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] _ in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
attemptStartingOnboarding()
|
|
||||||
|
|
||||||
setupSessionVerificationRequestsObserver()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
settingsFlowCoordinator.actions.sink { [weak self] action in
|
settingsFlowCoordinator.actions.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
@@ -374,7 +335,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .dismissedSettings:
|
case .dismissedSettings:
|
||||||
stateMachine.processEvent(.dismissedSettingsScreen)
|
stateMachine.processEvent(.dismissedSettingsScreen)
|
||||||
case .runLogoutFlow:
|
case .runLogoutFlow:
|
||||||
Task { await self.runLogoutFlow() }
|
actionsSubject.send(.logout)
|
||||||
case .clearCache:
|
case .clearCache:
|
||||||
actionsSubject.send(.clearCache)
|
actionsSubject.send(.clearCache)
|
||||||
case .forceLogout:
|
case .forceLogout:
|
||||||
@@ -413,17 +374,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
onboardingFlowCoordinator.actions
|
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .logout:
|
|
||||||
logout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processDecryptionError(_ info: UnableToDecryptInfo) {
|
private func processDecryptionError(_ info: UnableToDecryptInfo) {
|
||||||
@@ -455,53 +405,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
wasVisibleToUser: nil)
|
wasVisibleToUser: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupSessionVerificationRequestsObserver() {
|
|
||||||
userSession.clientProxy.sessionVerificationController?.actions
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self, case .receivedVerificationRequest(let details) = action else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.info("Received session verification request")
|
|
||||||
|
|
||||||
if details.senderProfile.userID == userSession.clientProxy.userID {
|
|
||||||
presentSessionVerificationScreen(flow: .deviceResponder(requestDetails: details))
|
|
||||||
} else {
|
|
||||||
presentSessionVerificationScreen(flow: .userResponder(requestDetails: details))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentSessionVerificationScreen(flow: SessionVerificationScreenFlow) {
|
|
||||||
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
|
||||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
|
||||||
}
|
|
||||||
|
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
|
||||||
|
|
||||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
|
||||||
flow: flow,
|
|
||||||
appSettings: appSettings,
|
|
||||||
mediaProvider: userSession.mediaProvider)
|
|
||||||
|
|
||||||
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
|
||||||
|
|
||||||
coordinator.actions
|
|
||||||
.sink { [weak self] action in
|
|
||||||
switch action {
|
|
||||||
case .done:
|
|
||||||
self?.navigationSplitCoordinator.setSheetCoordinator(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
|
||||||
|
|
||||||
navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentHomeScreen() {
|
private func presentHomeScreen() {
|
||||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
||||||
bugReportService: bugReportService,
|
bugReportService: bugReportService,
|
||||||
@@ -543,7 +446,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .presentGlobalSearch:
|
case .presentGlobalSearch:
|
||||||
presentGlobalSearch()
|
presentGlobalSearch()
|
||||||
case .logout:
|
case .logout:
|
||||||
Task { await self.runLogoutFlow() }
|
actionsSubject.send(.logout)
|
||||||
case .presentDeclineAndBlock(let userID, let roomID):
|
case .presentDeclineAndBlock(let userID, let roomID):
|
||||||
stateMachine.processEvent(.presentDeclineAndBlockScreen(userID: userID, roomID: roomID))
|
stateMachine.processEvent(.presentDeclineAndBlockScreen(userID: userID, roomID: roomID))
|
||||||
}
|
}
|
||||||
@@ -604,55 +507,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func runLogoutFlow() async {
|
|
||||||
let secureBackupController = userSession.clientProxy.secureBackupController
|
|
||||||
|
|
||||||
guard case let .success(isLastDevice) = await userSession.clientProxy.isOnlyDeviceLeft() else {
|
|
||||||
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard isLastDevice else {
|
|
||||||
logout()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard secureBackupController.recoveryState.value == .enabled else {
|
|
||||||
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
|
|
||||||
title: L10n.screenSignoutRecoveryDisabledTitle,
|
|
||||||
message: L10n.screenSignoutRecoveryDisabledSubtitle,
|
|
||||||
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
|
||||||
self?.actionsSubject.send(.logout)
|
|
||||||
}, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in
|
|
||||||
self?.settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard secureBackupController.keyBackupState.value == .enabled else {
|
|
||||||
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
|
|
||||||
title: L10n.screenSignoutKeyBackupDisabledTitle,
|
|
||||||
message: L10n.screenSignoutKeyBackupDisabledSubtitle,
|
|
||||||
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
|
||||||
self?.actionsSubject.send(.logout)
|
|
||||||
}, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in
|
|
||||||
self?.settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
presentSecureBackupLogoutConfirmationScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func logout() {
|
|
||||||
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
|
|
||||||
title: L10n.screenSignoutConfirmationDialogTitle,
|
|
||||||
message: L10n.screenSignoutConfirmationDialogContent,
|
|
||||||
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
|
||||||
self?.actionsSubject.send(.logout)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Room Flow
|
// MARK: Room Flow
|
||||||
|
|
||||||
private func startRoomFlow(roomID: String,
|
private func startRoomFlow(roomID: String,
|
||||||
@@ -680,7 +534,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// Here we assume that the app is running and the call state is already up to date
|
// Here we assume that the app is running and the call state is already up to date
|
||||||
presentCallScreen(roomProxy: roomProxy, notifyOtherParticipants: !roomProxy.infoPublisher.value.hasRoomCall)
|
presentCallScreen(roomProxy: roomProxy, notifyOtherParticipants: !roomProxy.infoPublisher.value.hasRoomCall)
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
presentSessionVerificationScreen(flow: .userIntiator(userID: userID))
|
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
||||||
case .finished:
|
case .finished:
|
||||||
stateMachine.processEvent(.deselectRoom)
|
stateMachine.processEvent(.deselectRoom)
|
||||||
}
|
}
|
||||||
@@ -890,28 +744,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSecureBackupLogoutConfirmationScreen() {
|
|
||||||
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
|
|
||||||
appMediator: appMediator))
|
|
||||||
|
|
||||||
coordinator.actions
|
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .cancel:
|
|
||||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
|
||||||
case .settings:
|
|
||||||
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
|
||||||
case .logout:
|
|
||||||
actionsSubject.send(.logout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
navigationSplitCoordinator.setSheetCoordinator(coordinator, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Global search
|
// MARK: Global search
|
||||||
|
|
||||||
private func presentGlobalSearch() {
|
private func presentGlobalSearch() {
|
||||||
|
|||||||
@@ -111,11 +111,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
/// Start chat has been dismissed
|
/// Start chat has been dismissed
|
||||||
case dismissedStartChatScreen
|
case dismissedStartChatScreen
|
||||||
|
|
||||||
/// Logout has been requested and this is the last session
|
|
||||||
case showLogoutConfirmationScreen
|
|
||||||
/// Logout has been cancelled
|
|
||||||
case dismissedLogoutConfirmationScreen
|
|
||||||
|
|
||||||
/// Request presentation of the room directory search screen.
|
/// Request presentation of the room directory search screen.
|
||||||
case showRoomDirectorySearchScreen
|
case showRoomDirectorySearchScreen
|
||||||
/// The room directory search screen has been dismissed.
|
/// The room directory search screen has been dismissed.
|
||||||
@@ -187,11 +182,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
case (.startChatScreen(let roomListSelectedRoomID), .dismissedStartChatScreen):
|
case (.startChatScreen(let roomListSelectedRoomID), .dismissedStartChatScreen):
|
||||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||||
|
|
||||||
case (.roomList(let roomListSelectedRoomID), .showLogoutConfirmationScreen):
|
|
||||||
return .logoutConfirmationScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
|
||||||
case (.logoutConfirmationScreen(let roomListSelectedRoomID), .dismissedLogoutConfirmationScreen):
|
|
||||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
|
||||||
|
|
||||||
case (.roomList(let roomListSelectedRoomID), .showRoomDirectorySearchScreen):
|
case (.roomList(let roomListSelectedRoomID), .showRoomDirectorySearchScreen):
|
||||||
return .roomDirectorySearchScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
return .roomDirectorySearchScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||||
case (.roomDirectorySearchScreen(let roomListSelectedRoomID), .dismissedRoomDirectorySearchScreen):
|
case (.roomDirectorySearchScreen(let roomListSelectedRoomID), .dismissedRoomDirectorySearchScreen):
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import Foundation
|
|||||||
import SwiftState
|
import SwiftState
|
||||||
|
|
||||||
enum OnboardingFlowCoordinatorAction {
|
enum OnboardingFlowCoordinatorAction {
|
||||||
|
case requestPresentation(animated: Bool)
|
||||||
|
case dismiss
|
||||||
case logout
|
case logout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +21,6 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private let analyticsService: AnalyticsService
|
private let analyticsService: AnalyticsService
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
private let notificationManager: NotificationManagerProtocol
|
private let notificationManager: NotificationManagerProtocol
|
||||||
private let rootNavigationStackCoordinator: NavigationStackCoordinator
|
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
private let windowManager: WindowManagerProtocol
|
private let windowManager: WindowManagerProtocol
|
||||||
private let isNewLogin: Bool
|
private let isNewLogin: Bool
|
||||||
@@ -74,8 +75,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
self.windowManager = windowManager
|
self.windowManager = windowManager
|
||||||
self.isNewLogin = isNewLogin
|
self.isNewLogin = isNewLogin
|
||||||
|
|
||||||
rootNavigationStackCoordinator = navigationStackCoordinator
|
self.navigationStackCoordinator = navigationStackCoordinator
|
||||||
self.navigationStackCoordinator = NavigationStackCoordinator()
|
|
||||||
|
|
||||||
stateMachine = .init(state: .initial)
|
stateMachine = .init(state: .initial)
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
fatalError("This flow coordinator shouldn't have been started")
|
fatalError("This flow coordinator shouldn't have been started")
|
||||||
}
|
}
|
||||||
|
|
||||||
rootNavigationStackCoordinator.setFullScreenCoverCoordinator(navigationStackCoordinator, animated: !isNewLogin)
|
actionsSubject.send(.requestPresentation(animated: !isNewLogin))
|
||||||
|
|
||||||
stateMachine.tryEvent(.next)
|
stateMachine.tryEvent(.next)
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (_, _, .notificationPermissions):
|
case (_, _, .notificationPermissions):
|
||||||
presentNotificationPermissionsScreen()
|
presentNotificationPermissionsScreen()
|
||||||
case (_, _, .finished):
|
case (_, _, .finished):
|
||||||
rootNavigationStackCoordinator.setFullScreenCoverCoordinator(nil)
|
actionsSubject.send(.dismiss)
|
||||||
stateMachine.tryState(.initial)
|
stateMachine.tryState(.initial)
|
||||||
case (.finished, _, .initial):
|
case (.finished, _, .initial):
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import SwiftUI
|
|||||||
enum UserSessionFlowCoordinatorAction {
|
enum UserSessionFlowCoordinatorAction {
|
||||||
case logout
|
case logout
|
||||||
case clearCache
|
case clearCache
|
||||||
/// Logout without a confirmation. The user forgot their PIN.
|
/// Logout and disable App Lock without any confirmation. The user forgot their PIN.
|
||||||
case forceLogout
|
case forceLogout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +21,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private let userSession: UserSessionProtocol
|
private let userSession: UserSessionProtocol
|
||||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||||
private let navigationTabCoordinator: NavigationTabCoordinator
|
private let navigationTabCoordinator: NavigationTabCoordinator
|
||||||
|
private let appMediator: AppMediatorProtocol
|
||||||
|
private let appSettings: AppSettings
|
||||||
|
|
||||||
|
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
|
||||||
|
private let onboardingStackCoordinator: NavigationStackCoordinator
|
||||||
private let chatsFlowCoordinator: ChatsFlowCoordinator
|
private let chatsFlowCoordinator: ChatsFlowCoordinator
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
|
||||||
@@ -45,6 +49,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
isNewLogin: Bool) {
|
isNewLogin: Bool) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
self.navigationRootCoordinator = navigationRootCoordinator
|
self.navigationRootCoordinator = navigationRootCoordinator
|
||||||
|
self.appMediator = appMediator
|
||||||
|
self.appSettings = appSettings
|
||||||
|
|
||||||
navigationTabCoordinator = NavigationTabCoordinator()
|
navigationTabCoordinator = NavigationTabCoordinator()
|
||||||
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
|
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
|
||||||
@@ -63,28 +69,31 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
notificationManager: notificationManager,
|
notificationManager: notificationManager,
|
||||||
isNewLogin: isNewLogin)
|
isNewLogin: isNewLogin)
|
||||||
|
|
||||||
|
onboardingStackCoordinator = NavigationStackCoordinator()
|
||||||
|
onboardingFlowCoordinator = OnboardingFlowCoordinator(userSession: userSession,
|
||||||
|
appLockService: appLockService,
|
||||||
|
analyticsService: analytics,
|
||||||
|
appSettings: appSettings,
|
||||||
|
notificationManager: notificationManager,
|
||||||
|
navigationStackCoordinator: onboardingStackCoordinator,
|
||||||
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
|
windowManager: appMediator.windowManager,
|
||||||
|
isNewLogin: isNewLogin)
|
||||||
|
|
||||||
navigationTabCoordinator.setTabs([
|
navigationTabCoordinator.setTabs([
|
||||||
.init(coordinator: chatsSplitCoordinator, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid),
|
.init(coordinator: chatsSplitCoordinator, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid),
|
||||||
.init(coordinator: BlankFormCoordinator(), title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid)
|
.init(coordinator: BlankFormCoordinator(), title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid)
|
||||||
])
|
])
|
||||||
|
|
||||||
chatsFlowCoordinator.actionsPublisher
|
setupObservers()
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
switch action {
|
|
||||||
case .logout:
|
|
||||||
actionsSubject.send(.logout)
|
|
||||||
case .clearCache:
|
|
||||||
actionsSubject.send(.clearCache)
|
|
||||||
case .forceLogout:
|
|
||||||
actionsSubject.send(.forceLogout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
|
#warning("This flow still needs a state machine.")
|
||||||
|
|
||||||
chatsFlowCoordinator.start()
|
chatsFlowCoordinator.start()
|
||||||
|
|
||||||
|
attemptStartingOnboarding()
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
@@ -92,6 +101,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
|
// There aren't any routes that directly target this flow yet, so pass them directly to the
|
||||||
|
// chats flow coordinator.
|
||||||
|
#warning("This should switch tabs to make sure the route is visible.")
|
||||||
chatsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
chatsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +111,189 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
chatsFlowCoordinator.clearRoute(animated: animated)
|
chatsFlowCoordinator.clearRoute(animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
#warning("Should this be a publisher instead??")
|
#warning("This should use a publisher, combining it with the active tab.")
|
||||||
func isDisplayingRoomScreen(withRoomID roomID: String) -> Bool {
|
func isDisplayingRoomScreen(withRoomID roomID: String) -> Bool {
|
||||||
chatsFlowCoordinator.isDisplayingRoomScreen(withRoomID: roomID)
|
chatsFlowCoordinator.isDisplayingRoomScreen(withRoomID: roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupObservers() {
|
||||||
|
chatsFlowCoordinator.actionsPublisher
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .logout:
|
||||||
|
Task { await self.runLogoutFlow() }
|
||||||
|
case .sessionVerification(let flow):
|
||||||
|
presentSessionVerificationScreen(flow: flow)
|
||||||
|
case .clearCache:
|
||||||
|
actionsSubject.send(.clearCache)
|
||||||
|
case .forceLogout:
|
||||||
|
actionsSubject.send(.forceLogout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
userSession.sessionSecurityStatePublisher
|
||||||
|
.map(\.verificationState)
|
||||||
|
.filter { $0 != .unknown }
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
attemptStartingOnboarding()
|
||||||
|
setupSessionVerificationRequestsObserver()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
onboardingFlowCoordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .requestPresentation(let animated):
|
||||||
|
navigationTabCoordinator.setFullScreenCoverCoordinator(onboardingStackCoordinator, animated: animated)
|
||||||
|
case .dismiss:
|
||||||
|
navigationTabCoordinator.setFullScreenCoverCoordinator(nil)
|
||||||
|
case .logout:
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Onboarding
|
||||||
|
|
||||||
|
func attemptStartingOnboarding() {
|
||||||
|
MXLog.info("Attempting to start onboarding")
|
||||||
|
|
||||||
|
if onboardingFlowCoordinator.shouldStart {
|
||||||
|
clearRoute(animated: false)
|
||||||
|
onboardingFlowCoordinator.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Session Verification
|
||||||
|
|
||||||
|
private func setupSessionVerificationRequestsObserver() {
|
||||||
|
userSession.clientProxy.sessionVerificationController?.actions
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self, case .receivedVerificationRequest(let details) = action else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MXLog.info("Received session verification request")
|
||||||
|
|
||||||
|
if details.senderProfile.userID == userSession.clientProxy.userID {
|
||||||
|
presentSessionVerificationScreen(flow: .deviceResponder(requestDetails: details))
|
||||||
|
} else {
|
||||||
|
presentSessionVerificationScreen(flow: .userResponder(requestDetails: details))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentSessionVerificationScreen(flow: SessionVerificationScreenFlow) {
|
||||||
|
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
||||||
|
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||||
|
}
|
||||||
|
|
||||||
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||||
|
flow: flow,
|
||||||
|
appSettings: appSettings,
|
||||||
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
|
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
coordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
switch action {
|
||||||
|
case .done:
|
||||||
|
self?.navigationTabCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
|
||||||
|
navigationTabCoordinator.setSheetCoordinator(navigationStackCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Logout
|
||||||
|
|
||||||
|
private func runLogoutFlow() async {
|
||||||
|
let secureBackupController = userSession.clientProxy.secureBackupController
|
||||||
|
|
||||||
|
guard case let .success(isLastDevice) = await userSession.clientProxy.isOnlyDeviceLeft() else {
|
||||||
|
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard isLastDevice else {
|
||||||
|
logout()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard secureBackupController.recoveryState.value == .enabled else {
|
||||||
|
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
|
||||||
|
title: L10n.screenSignoutRecoveryDisabledTitle,
|
||||||
|
message: L10n.screenSignoutRecoveryDisabledSubtitle,
|
||||||
|
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.logout)
|
||||||
|
}, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in
|
||||||
|
self?.chatsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard secureBackupController.keyBackupState.value == .enabled else {
|
||||||
|
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
|
||||||
|
title: L10n.screenSignoutKeyBackupDisabledTitle,
|
||||||
|
message: L10n.screenSignoutKeyBackupDisabledSubtitle,
|
||||||
|
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.logout)
|
||||||
|
}, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in
|
||||||
|
self?.chatsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
presentSecureBackupLogoutConfirmationScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logout() {
|
||||||
|
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
|
||||||
|
title: L10n.screenSignoutConfirmationDialogTitle,
|
||||||
|
message: L10n.screenSignoutConfirmationDialogContent,
|
||||||
|
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.logout)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentSecureBackupLogoutConfirmationScreen() {
|
||||||
|
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
|
||||||
|
appMediator: appMediator))
|
||||||
|
|
||||||
|
coordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .cancel:
|
||||||
|
navigationTabCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .settings:
|
||||||
|
chatsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||||
|
navigationTabCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .logout:
|
||||||
|
actionsSubject.send(.logout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
navigationTabCoordinator.setSheetCoordinator(coordinator, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -16,12 +16,12 @@ enum SessionVerificationScreenCoordinatorAction {
|
|||||||
enum SessionVerificationScreenFlow {
|
enum SessionVerificationScreenFlow {
|
||||||
case deviceInitiator
|
case deviceInitiator
|
||||||
case deviceResponder(requestDetails: SessionVerificationRequestDetails)
|
case deviceResponder(requestDetails: SessionVerificationRequestDetails)
|
||||||
case userIntiator(userID: String)
|
case userInitiator(userID: String)
|
||||||
case userResponder(requestDetails: SessionVerificationRequestDetails)
|
case userResponder(requestDetails: SessionVerificationRequestDetails)
|
||||||
|
|
||||||
var isResponder: Bool {
|
var isResponder: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .deviceInitiator, .userIntiator:
|
case .deviceInitiator, .userInitiator:
|
||||||
false
|
false
|
||||||
case .deviceResponder, .userResponder:
|
case .deviceResponder, .userResponder:
|
||||||
true
|
true
|
||||||
|
|||||||
+6
-6
@@ -36,7 +36,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator, .deviceResponder:
|
case .deviceInitiator, .deviceResponder:
|
||||||
return (\.devices, .defaultSolid)
|
return (\.devices, .defaultSolid)
|
||||||
case .userIntiator, .userResponder:
|
case .userInitiator, .userResponder:
|
||||||
return (\.userProfileSolid, .defaultSolid)
|
return (\.userProfileSolid, .defaultSolid)
|
||||||
}
|
}
|
||||||
case .acceptingVerificationRequest:
|
case .acceptingVerificationRequest:
|
||||||
@@ -74,7 +74,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator:
|
case .deviceInitiator:
|
||||||
return L10n.screenSessionVerificationUseAnotherDeviceTitle
|
return L10n.screenSessionVerificationUseAnotherDeviceTitle
|
||||||
case .userIntiator:
|
case .userInitiator:
|
||||||
return L10n.screenSessionVerificationUserInitiatorTitle
|
return L10n.screenSessionVerificationUserInitiatorTitle
|
||||||
case .deviceResponder, .userResponder:
|
case .deviceResponder, .userResponder:
|
||||||
return L10n.screenSessionVerificationRequestTitle
|
return L10n.screenSessionVerificationRequestTitle
|
||||||
@@ -108,7 +108,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator, .deviceResponder:
|
case .deviceInitiator, .deviceResponder:
|
||||||
return L10n.screenSessionVerificationWaitingOtherDeviceTitle
|
return L10n.screenSessionVerificationWaitingOtherDeviceTitle
|
||||||
case .userIntiator, .userResponder:
|
case .userInitiator, .userResponder:
|
||||||
return L10n.screenSessionVerificationWaitingOtherUserTitle
|
return L10n.screenSessionVerificationWaitingOtherUserTitle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator:
|
case .deviceInitiator:
|
||||||
return L10n.screenSessionVerificationUseAnotherDeviceSubtitle
|
return L10n.screenSessionVerificationUseAnotherDeviceSubtitle
|
||||||
case .userIntiator:
|
case .userInitiator:
|
||||||
return L10n.screenSessionVerificationUserInitiatorSubtitle
|
return L10n.screenSessionVerificationUserInitiatorSubtitle
|
||||||
case .deviceResponder:
|
case .deviceResponder:
|
||||||
return L10n.screenSessionVerificationRequestSubtitle
|
return L10n.screenSessionVerificationRequestSubtitle
|
||||||
@@ -146,14 +146,14 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator, .deviceResponder:
|
case .deviceInitiator, .deviceResponder:
|
||||||
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||||
case .userIntiator, .userResponder:
|
case .userInitiator, .userResponder:
|
||||||
return L10n.screenSessionVerificationCompareEmojisUserSubtitle
|
return L10n.screenSessionVerificationCompareEmojisUserSubtitle
|
||||||
}
|
}
|
||||||
case .verified:
|
case .verified:
|
||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator, .deviceResponder:
|
case .deviceInitiator, .deviceResponder:
|
||||||
return L10n.screenSessionVerificationCompleteSubtitle
|
return L10n.screenSessionVerificationCompleteSubtitle
|
||||||
case .userIntiator, .userResponder:
|
case .userInitiator, .userResponder:
|
||||||
return L10n.screenSessionVerificationCompleteUserSubtitle
|
return L10n.screenSessionVerificationCompleteUserSubtitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -178,7 +178,7 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
switch flow {
|
switch flow {
|
||||||
case .deviceInitiator:
|
case .deviceInitiator:
|
||||||
return await sessionVerificationControllerProxy.requestDeviceVerification()
|
return await sessionVerificationControllerProxy.requestDeviceVerification()
|
||||||
case .userIntiator(let userID):
|
case .userInitiator(let userID):
|
||||||
return await sessionVerificationControllerProxy.requestUserVerification(userID)
|
return await sessionVerificationControllerProxy.requestUserVerification(userID)
|
||||||
default:
|
default:
|
||||||
fatalError("Incorrect API usage.")
|
fatalError("Incorrect API usage.")
|
||||||
|
|||||||
+5
-5
@@ -43,7 +43,7 @@ struct SessionVerificationScreen: View {
|
|||||||
private var toolbar: some ToolbarContent {
|
private var toolbar: some ToolbarContent {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
switch context.viewState.flow {
|
switch context.viewState.flow {
|
||||||
case .userIntiator, .userResponder:
|
case .userInitiator, .userResponder:
|
||||||
Button(L10n.actionCancel) {
|
Button(L10n.actionCancel) {
|
||||||
context.send(viewAction: .cancel)
|
context.send(viewAction: .cancel)
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ struct SessionVerificationScreen: View {
|
|||||||
SessionVerificationRequestDetailsView(details: details,
|
SessionVerificationRequestDetailsView(details: details,
|
||||||
isUserVerification: true,
|
isUserVerification: true,
|
||||||
mediaProvider: context.mediaProvider)
|
mediaProvider: context.mediaProvider)
|
||||||
case .userIntiator:
|
case .userInitiator:
|
||||||
Button(L10n.actionLearnMore) {
|
Button(L10n.actionLearnMore) {
|
||||||
UIApplication.shared.open(context.viewState.learnMoreURL)
|
UIApplication.shared.open(context.viewState.learnMoreURL)
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ struct SessionVerificationScreen: View {
|
|||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
switch context.viewState.flow {
|
switch context.viewState.flow {
|
||||||
case .deviceInitiator, .userIntiator:
|
case .deviceInitiator, .userInitiator:
|
||||||
Button(L10n.actionStartVerification) {
|
Button(L10n.actionStartVerification) {
|
||||||
context.send(viewAction: .requestVerification)
|
context.send(viewAction: .requestVerification)
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ struct SessionVerificationScreen: View {
|
|||||||
}
|
}
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
switch context.viewState.flow {
|
switch context.viewState.flow {
|
||||||
case .deviceInitiator, .userIntiator:
|
case .deviceInitiator, .userInitiator:
|
||||||
Button(L10n.actionRetry) {
|
Button(L10n.actionRetry) {
|
||||||
context.send(viewAction: .restart)
|
context.send(viewAction: .restart)
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
|||||||
sessionVerificationScreen(state: .initial, flow: .deviceInitiator)
|
sessionVerificationScreen(state: .initial, flow: .deviceInitiator)
|
||||||
.previewDisplayName("Initial - Device Initiator")
|
.previewDisplayName("Initial - Device Initiator")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .initial, flow: .userIntiator(userID: "@bob:matrix.org"))
|
sessionVerificationScreen(state: .initial, flow: .userInitiator(userID: "@bob:matrix.org"))
|
||||||
.previewDisplayName("Initial - User Initiator")
|
.previewDisplayName("Initial - User Initiator")
|
||||||
|
|
||||||
let details = SessionVerificationRequestDetails(senderProfile: UserProfileProxy(userID: "@bob:matrix.org",
|
let details = SessionVerificationRequestDetails(senderProfile: UserProfileProxy(userID: "@bob:matrix.org",
|
||||||
|
|||||||
Reference in New Issue
Block a user