mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Incoming session verification support (#3428)
* Fixes #1227 - Add support for receiving and interacting with incoming session verification requests. * Fix a couple of random small warnings * Move static view config to the view state * Update snapshots
This commit is contained in:
parent
cf5e9fb313
commit
d77bb935b7
@ -666,6 +666,7 @@
|
|||||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||||
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
||||||
|
91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */; };
|
||||||
92012C96039BC8C2CAEBA9E2 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671C338B7259DC5774816885 /* AuthenticationServiceTests.swift */; };
|
92012C96039BC8C2CAEBA9E2 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671C338B7259DC5774816885 /* AuthenticationServiceTests.swift */; };
|
||||||
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
||||||
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; };
|
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; };
|
||||||
@ -1314,6 +1315,7 @@
|
|||||||
190EC7285D3CFEF0D3011BCF /* GeoURI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURI.swift; sourceTree = "<group>"; };
|
190EC7285D3CFEF0D3011BCF /* GeoURI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURI.swift; sourceTree = "<group>"; };
|
||||||
196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTrackerViewModifier.swift; sourceTree = "<group>"; };
|
196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTrackerViewModifier.swift; sourceTree = "<group>"; };
|
||||||
1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderProtocol.swift; sourceTree = "<group>"; };
|
1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationRequestDetailsView.swift; sourceTree = "<group>"; };
|
||||||
1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = "<group>"; };
|
1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = "<group>"; };
|
||||||
1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMap.swift; sourceTree = "<group>"; };
|
1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMap.swift; sourceTree = "<group>"; };
|
||||||
1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; };
|
1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; };
|
||||||
@ -4650,6 +4652,7 @@
|
|||||||
A722D372674EE5687E1A67E4 /* View */ = {
|
A722D372674EE5687E1A67E4 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */,
|
||||||
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */,
|
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
@ -6904,6 +6907,7 @@
|
|||||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||||
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
||||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
||||||
|
91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */,
|
||||||
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */,
|
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */,
|
||||||
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */,
|
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */,
|
||||||
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */,
|
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */,
|
||||||
@ -7828,7 +7832,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.0.61;
|
version = 1.0.62;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
@ -149,8 +149,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "2e6378514e79a648d436e8faeb8cd8106910cf0b",
|
"revision" : "9b26e40ae6c27c56e233577c863569ff074f84fd",
|
||||||
"version" : "1.0.61"
|
"version" : "1.0.62"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -115,8 +115,9 @@
|
|||||||
"banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later.";
|
"banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later.";
|
||||||
"banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app.";
|
"banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app.";
|
||||||
"banner_migrate_to_native_sliding_sync_title" = "Upgrade available";
|
"banner_migrate_to_native_sliding_sync_title" = "Upgrade available";
|
||||||
"banner.set_up_recovery.content" = "Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices.";
|
"banner_set_up_recovery_submit" = "Set up recovery";
|
||||||
"banner.set_up_recovery.title" = "Set up recovery";
|
"banner.set_up_recovery.content" = "Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices.";
|
||||||
|
"banner.set_up_recovery.title" = "Set up recovery to protect your account";
|
||||||
"common_about" = "About";
|
"common_about" = "About";
|
||||||
"common_acceptable_use_policy" = "Acceptable use policy";
|
"common_acceptable_use_policy" = "Acceptable use policy";
|
||||||
"common_advanced_settings" = "Advanced settings";
|
"common_advanced_settings" = "Advanced settings";
|
||||||
@ -454,7 +455,7 @@
|
|||||||
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
|
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
|
||||||
"screen_change_server_subtitle" = "What is the address of your server?";
|
"screen_change_server_subtitle" = "What is the address of your server?";
|
||||||
"screen_change_server_title" = "Select your server";
|
"screen_change_server_title" = "Select your server";
|
||||||
"screen_chat_backup_key_backup_action_disable" = "Turn off backup";
|
"screen_chat_backup_key_backup_action_disable" = "Delete key storage";
|
||||||
"screen_chat_backup_key_backup_action_enable" = "Turn on backup";
|
"screen_chat_backup_key_backup_action_enable" = "Turn on backup";
|
||||||
"screen_chat_backup_key_backup_description" = "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices. %1$@.";
|
"screen_chat_backup_key_backup_description" = "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices. %1$@.";
|
||||||
"screen_chat_backup_key_backup_title" = "Key storage";
|
"screen_chat_backup_key_backup_title" = "Key storage";
|
||||||
@ -538,10 +539,10 @@
|
|||||||
"screen_key_backup_disable_confirmation_action_turn_off" = "Turn off";
|
"screen_key_backup_disable_confirmation_action_turn_off" = "Turn off";
|
||||||
"screen_key_backup_disable_confirmation_description" = "You will lose your encrypted messages if you are signed out of all devices.";
|
"screen_key_backup_disable_confirmation_description" = "You will lose your encrypted messages if you are signed out of all devices.";
|
||||||
"screen_key_backup_disable_confirmation_title" = "Are you sure you want to turn off backup?";
|
"screen_key_backup_disable_confirmation_title" = "Are you sure you want to turn off backup?";
|
||||||
"screen_key_backup_disable_description" = "Turning off backup will remove your current encryption key backup and turn off other security features. In this case, you will:";
|
"screen_key_backup_disable_description" = "Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:";
|
||||||
"screen_key_backup_disable_description_point_1" = "Not have encrypted message history on new devices";
|
"screen_key_backup_disable_description_point_1" = "You will not have encrypted message history on new devices";
|
||||||
"screen_key_backup_disable_description_point_2" = "Lose access to your encrypted messages if you are signed out of %1$@ everywhere";
|
"screen_key_backup_disable_description_point_2" = "You will lose access to your encrypted messages if you are signed out of %1$@ everywhere";
|
||||||
"screen_key_backup_disable_title" = "Are you sure you want to turn off backup?";
|
"screen_key_backup_disable_title" = "Are you sure you want to turn off key storage and delete it?";
|
||||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||||
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
||||||
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
||||||
@ -652,7 +653,7 @@
|
|||||||
"screen_recovery_key_save_title" = "Save your recovery key somewhere safe";
|
"screen_recovery_key_save_title" = "Save your recovery key somewhere safe";
|
||||||
"screen_recovery_key_setup_confirmation_description" = "You will not be able to access your new recovery key after this step.";
|
"screen_recovery_key_setup_confirmation_description" = "You will not be able to access your new recovery key after this step.";
|
||||||
"screen_recovery_key_setup_confirmation_title" = "Have you saved your recovery key?";
|
"screen_recovery_key_setup_confirmation_title" = "Have you saved your recovery key?";
|
||||||
"screen_recovery_key_setup_description" = "Your chat backup is protected by a recovery key. If you need a new recovery key after setup you can recreate by selecting ‘Change recovery key’.";
|
"screen_recovery_key_setup_description" = "Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting ‘Change recovery key’.";
|
||||||
"screen_recovery_key_setup_generate_key" = "Generate your recovery key";
|
"screen_recovery_key_setup_generate_key" = "Generate your recovery key";
|
||||||
"screen_recovery_key_setup_generate_key_description" = "Do not share this with anyone!";
|
"screen_recovery_key_setup_generate_key_description" = "Do not share this with anyone!";
|
||||||
"screen_recovery_key_setup_success" = "Recovery setup successful";
|
"screen_recovery_key_setup_success" = "Recovery setup successful";
|
||||||
|
@ -244,9 +244,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .otherDevice:
|
case .otherDevice:
|
||||||
Task {
|
presentSessionVerificationScreen()
|
||||||
await self.presentSessionVerificationScreen()
|
|
||||||
}
|
|
||||||
case .recoveryKey:
|
case .recoveryKey:
|
||||||
presentRecoveryKeyScreen()
|
presentRecoveryKeyScreen()
|
||||||
case .skip:
|
case .skip:
|
||||||
@ -263,12 +261,13 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentCoordinator(coordinator)
|
presentCoordinator(coordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSessionVerificationScreen() async {
|
private func presentSessionVerificationScreen() {
|
||||||
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
|
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
||||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||||
|
flow: .initiator)
|
||||||
|
|
||||||
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
|
import MatrixRustSDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum UserSessionFlowCoordinatorAction {
|
enum UserSessionFlowCoordinatorAction {
|
||||||
@ -59,7 +60,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
/// For testing purposes.
|
/// For testing purposes.
|
||||||
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(userSession: UserSessionProtocol,
|
||||||
navigationRootCoordinator: NavigationRootCoordinator,
|
navigationRootCoordinator: NavigationRootCoordinator,
|
||||||
appLockService: AppLockServiceProtocol,
|
appLockService: AppLockServiceProtocol,
|
||||||
@ -113,87 +113,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
|
|
||||||
userSession.sessionSecurityStatePublisher
|
setupObservers()
|
||||||
.map(\.verificationState)
|
|
||||||
.filter { $0 != .unknown }
|
|
||||||
.removeDuplicates()
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] _ in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
attemptStartingOnboarding()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
settingsFlowCoordinator.actions.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .presentedSettings:
|
|
||||||
stateMachine.processEvent(.showSettingsScreen)
|
|
||||||
case .dismissedSettings:
|
|
||||||
stateMachine.processEvent(.dismissedSettingsScreen)
|
|
||||||
case .runLogoutFlow:
|
|
||||||
Task { await self.runLogoutFlow() }
|
|
||||||
case .clearCache:
|
|
||||||
actionsSubject.send(.clearCache)
|
|
||||||
case .forceLogout:
|
|
||||||
actionsSubject.send(.forceLogout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
userSession.clientProxy.actionsPublisher
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { action in
|
|
||||||
guard case let .receivedDecryptionError(info) = action else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeToDecryptMs: Int
|
|
||||||
if let unsignedTimeToDecryptMs = info.timeToDecryptMs {
|
|
||||||
timeToDecryptMs = Int(unsignedTimeToDecryptMs)
|
|
||||||
} else {
|
|
||||||
timeToDecryptMs = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
switch info.cause {
|
|
||||||
case .unknown:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .UnknownError, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .unknownDevice:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .unsignedDevice:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .verificationViolation:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedVerificationViolation, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .sentBeforeWeJoined:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedDueToMembership, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
elementCallService.actions
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] action in
|
|
||||||
switch action {
|
|
||||||
case .endCall:
|
|
||||||
self?.dismissCallScreenIfNeeded()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
onboardingFlowCoordinator.actions
|
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .logout:
|
|
||||||
logout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@ -385,6 +305,129 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupObservers() {
|
||||||
|
userSession.sessionSecurityStatePublisher
|
||||||
|
.map(\.verificationState)
|
||||||
|
.filter { $0 != .unknown }
|
||||||
|
.removeDuplicates()
|
||||||
|
.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
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .presentedSettings:
|
||||||
|
stateMachine.processEvent(.showSettingsScreen)
|
||||||
|
case .dismissedSettings:
|
||||||
|
stateMachine.processEvent(.dismissedSettingsScreen)
|
||||||
|
case .runLogoutFlow:
|
||||||
|
Task { await self.runLogoutFlow() }
|
||||||
|
case .clearCache:
|
||||||
|
actionsSubject.send(.clearCache)
|
||||||
|
case .forceLogout:
|
||||||
|
actionsSubject.send(.forceLogout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
userSession.clientProxy.actionsPublisher
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self, case let .receivedDecryptionError(info) = action else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeToDecryptMs: Int
|
||||||
|
if let unsignedTimeToDecryptMs = info.timeToDecryptMs {
|
||||||
|
timeToDecryptMs = Int(unsignedTimeToDecryptMs)
|
||||||
|
} else {
|
||||||
|
timeToDecryptMs = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
switch info.cause {
|
||||||
|
case .unknown:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .UnknownError, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .unknownDevice:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .unsignedDevice:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .verificationViolation:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedVerificationViolation, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .sentBeforeWeJoined:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedDueToMembership, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
elementCallService.actions
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] action in
|
||||||
|
switch action {
|
||||||
|
case .endCall:
|
||||||
|
self?.dismissCallScreenIfNeeded()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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 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")
|
||||||
|
|
||||||
|
presentSessionVerificationScreen(details: details)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentSessionVerificationScreen(details: SessionVerificationRequestDetails) {
|
||||||
|
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
||||||
|
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||||
|
}
|
||||||
|
|
||||||
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||||
|
flow: .responder(details: details))
|
||||||
|
|
||||||
|
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
coordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
switch action {
|
||||||
|
case .done:
|
||||||
|
self?.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
navigationSplitCoordinator.setSheetCoordinator(coordinator)
|
||||||
|
}
|
||||||
|
|
||||||
private func presentHomeScreen() {
|
private func presentHomeScreen() {
|
||||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
||||||
bugReportService: bugReportService,
|
bugReportService: bugReportService,
|
||||||
@ -569,7 +612,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
self?.stateMachine.processEvent(.dismissedStartChatScreen)
|
self?.stateMachine.processEvent(.dismissedStartChatScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Session Verification
|
||||||
|
|
||||||
// MARK: Calls
|
// MARK: Calls
|
||||||
|
|
||||||
private func presentCallScreen(genericCallLink url: URL) {
|
private func presentCallScreen(genericCallLink url: URL) {
|
||||||
|
@ -266,6 +266,8 @@ internal enum L10n {
|
|||||||
internal static var bannerMigrateToNativeSlidingSyncForceLogoutTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_force_logout_title") }
|
internal static var bannerMigrateToNativeSlidingSyncForceLogoutTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_force_logout_title") }
|
||||||
/// Upgrade available
|
/// Upgrade available
|
||||||
internal static var bannerMigrateToNativeSlidingSyncTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_title") }
|
internal static var bannerMigrateToNativeSlidingSyncTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_title") }
|
||||||
|
/// Set up recovery
|
||||||
|
internal static var bannerSetUpRecoverySubmit: String { return L10n.tr("Localizable", "banner_set_up_recovery_submit") }
|
||||||
/// About
|
/// About
|
||||||
internal static var commonAbout: String { return L10n.tr("Localizable", "common_about") }
|
internal static var commonAbout: String { return L10n.tr("Localizable", "common_about") }
|
||||||
/// Acceptable use policy
|
/// Acceptable use policy
|
||||||
@ -1009,7 +1011,7 @@ internal enum L10n {
|
|||||||
internal static var screenChangeServerSubtitle: String { return L10n.tr("Localizable", "screen_change_server_subtitle") }
|
internal static var screenChangeServerSubtitle: String { return L10n.tr("Localizable", "screen_change_server_subtitle") }
|
||||||
/// Select your server
|
/// Select your server
|
||||||
internal static var screenChangeServerTitle: String { return L10n.tr("Localizable", "screen_change_server_title") }
|
internal static var screenChangeServerTitle: String { return L10n.tr("Localizable", "screen_change_server_title") }
|
||||||
/// Turn off backup
|
/// Delete key storage
|
||||||
internal static var screenChatBackupKeyBackupActionDisable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_disable") }
|
internal static var screenChatBackupKeyBackupActionDisable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_disable") }
|
||||||
/// Turn on backup
|
/// Turn on backup
|
||||||
internal static var screenChatBackupKeyBackupActionEnable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_enable") }
|
internal static var screenChatBackupKeyBackupActionEnable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_enable") }
|
||||||
@ -1258,15 +1260,15 @@ internal enum L10n {
|
|||||||
internal static var screenKeyBackupDisableConfirmationDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_description") }
|
internal static var screenKeyBackupDisableConfirmationDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_description") }
|
||||||
/// Are you sure you want to turn off backup?
|
/// Are you sure you want to turn off backup?
|
||||||
internal static var screenKeyBackupDisableConfirmationTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_title") }
|
internal static var screenKeyBackupDisableConfirmationTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_title") }
|
||||||
/// Turning off backup will remove your current encryption key backup and turn off other security features. In this case, you will:
|
/// Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:
|
||||||
internal static var screenKeyBackupDisableDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_description") }
|
internal static var screenKeyBackupDisableDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_description") }
|
||||||
/// Not have encrypted message history on new devices
|
/// You will not have encrypted message history on new devices
|
||||||
internal static var screenKeyBackupDisableDescriptionPoint1: String { return L10n.tr("Localizable", "screen_key_backup_disable_description_point_1") }
|
internal static var screenKeyBackupDisableDescriptionPoint1: String { return L10n.tr("Localizable", "screen_key_backup_disable_description_point_1") }
|
||||||
/// Lose access to your encrypted messages if you are signed out of %1$@ everywhere
|
/// You will lose access to your encrypted messages if you are signed out of %1$@ everywhere
|
||||||
internal static func screenKeyBackupDisableDescriptionPoint2(_ p1: Any) -> String {
|
internal static func screenKeyBackupDisableDescriptionPoint2(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "screen_key_backup_disable_description_point_2", String(describing: p1))
|
return L10n.tr("Localizable", "screen_key_backup_disable_description_point_2", String(describing: p1))
|
||||||
}
|
}
|
||||||
/// Are you sure you want to turn off backup?
|
/// Are you sure you want to turn off key storage and delete it?
|
||||||
internal static var screenKeyBackupDisableTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_title") }
|
internal static var screenKeyBackupDisableTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_title") }
|
||||||
/// This account has been deactivated.
|
/// This account has been deactivated.
|
||||||
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
||||||
@ -1536,7 +1538,7 @@ internal enum L10n {
|
|||||||
internal static var screenRecoveryKeySetupConfirmationDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_description") }
|
internal static var screenRecoveryKeySetupConfirmationDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_description") }
|
||||||
/// Have you saved your recovery key?
|
/// Have you saved your recovery key?
|
||||||
internal static var screenRecoveryKeySetupConfirmationTitle: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_title") }
|
internal static var screenRecoveryKeySetupConfirmationTitle: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_title") }
|
||||||
/// Your chat backup is protected by a recovery key. If you need a new recovery key after setup you can recreate by selecting ‘Change recovery key’.
|
/// Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting ‘Change recovery key’.
|
||||||
internal static var screenRecoveryKeySetupDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_description") }
|
internal static var screenRecoveryKeySetupDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_description") }
|
||||||
/// Generate your recovery key
|
/// Generate your recovery key
|
||||||
internal static var screenRecoveryKeySetupGenerateKey: String { return L10n.tr("Localizable", "screen_recovery_key_setup_generate_key") }
|
internal static var screenRecoveryKeySetupGenerateKey: String { return L10n.tr("Localizable", "screen_recovery_key_setup_generate_key") }
|
||||||
@ -2499,9 +2501,9 @@ internal enum L10n {
|
|||||||
|
|
||||||
internal enum Banner {
|
internal enum Banner {
|
||||||
internal enum SetUpRecovery {
|
internal enum SetUpRecovery {
|
||||||
/// Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices.
|
/// Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices.
|
||||||
internal static var content: String { return L10n.tr("Localizable", "banner.set_up_recovery.content") }
|
internal static var content: String { return L10n.tr("Localizable", "banner.set_up_recovery.content") }
|
||||||
/// Set up recovery
|
/// Set up recovery to protect your account
|
||||||
internal static var title: String { return L10n.tr("Localizable", "banner.set_up_recovery.title") }
|
internal static var title: String { return L10n.tr("Localizable", "banner.set_up_recovery.title") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,6 @@ extension ClientProxyMock {
|
|||||||
logoutReturnValue = nil
|
logoutReturnValue = nil
|
||||||
searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false))
|
searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false))
|
||||||
profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user"))
|
profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user"))
|
||||||
sessionVerificationControllerProxyReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
|
|
||||||
ignoreUserReturnValue = .success(())
|
ignoreUserReturnValue = .success(())
|
||||||
unignoreUserReturnValue = .success(())
|
unignoreUserReturnValue = .success(())
|
||||||
|
|
||||||
|
@ -2210,6 +2210,7 @@ class ClientProxyMock: ClientProxyProtocol {
|
|||||||
set(value) { underlyingSecureBackupController = value }
|
set(value) { underlyingSecureBackupController = value }
|
||||||
}
|
}
|
||||||
var underlyingSecureBackupController: SecureBackupControllerProtocol!
|
var underlyingSecureBackupController: SecureBackupControllerProtocol!
|
||||||
|
var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||||
|
|
||||||
//MARK: - isOnlyDeviceLeft
|
//MARK: - isOnlyDeviceLeft
|
||||||
|
|
||||||
@ -3519,70 +3520,6 @@ class ClientProxyMock: ClientProxyProtocol {
|
|||||||
return removeUserAvatarReturnValue
|
return removeUserAvatarReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - sessionVerificationControllerProxy
|
|
||||||
|
|
||||||
var sessionVerificationControllerProxyUnderlyingCallsCount = 0
|
|
||||||
var sessionVerificationControllerProxyCallsCount: Int {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return sessionVerificationControllerProxyUnderlyingCallsCount
|
|
||||||
} else {
|
|
||||||
var returnValue: Int? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = sessionVerificationControllerProxyUnderlyingCallsCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
sessionVerificationControllerProxyUnderlyingCallsCount = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
sessionVerificationControllerProxyUnderlyingCallsCount = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var sessionVerificationControllerProxyCalled: Bool {
|
|
||||||
return sessionVerificationControllerProxyCallsCount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessionVerificationControllerProxyUnderlyingReturnValue: Result<SessionVerificationControllerProxyProtocol, ClientProxyError>!
|
|
||||||
var sessionVerificationControllerProxyReturnValue: Result<SessionVerificationControllerProxyProtocol, ClientProxyError>! {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return sessionVerificationControllerProxyUnderlyingReturnValue
|
|
||||||
} else {
|
|
||||||
var returnValue: Result<SessionVerificationControllerProxyProtocol, ClientProxyError>? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = sessionVerificationControllerProxyUnderlyingReturnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
sessionVerificationControllerProxyUnderlyingReturnValue = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
sessionVerificationControllerProxyUnderlyingReturnValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var sessionVerificationControllerProxyClosure: (() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>)?
|
|
||||||
|
|
||||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
|
||||||
sessionVerificationControllerProxyCallsCount += 1
|
|
||||||
if let sessionVerificationControllerProxyClosure = sessionVerificationControllerProxyClosure {
|
|
||||||
return await sessionVerificationControllerProxyClosure()
|
|
||||||
} else {
|
|
||||||
return sessionVerificationControllerProxyReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - deactivateAccount
|
//MARK: - deactivateAccount
|
||||||
|
|
||||||
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
|
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
|
||||||
@ -13435,12 +13372,146 @@ class SecureBackupControllerMock: SecureBackupControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
var actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> {
|
||||||
get { return underlyingCallbacks }
|
get { return underlyingActions }
|
||||||
set(value) { underlyingCallbacks = value }
|
set(value) { underlyingActions = value }
|
||||||
}
|
}
|
||||||
var underlyingCallbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never>!
|
var underlyingActions: PassthroughSubject<SessionVerificationControllerProxyAction, Never>!
|
||||||
|
|
||||||
|
//MARK: - acknowledgeVerificationRequest
|
||||||
|
|
||||||
|
var acknowledgeVerificationRequestDetailsUnderlyingCallsCount = 0
|
||||||
|
var acknowledgeVerificationRequestDetailsCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acknowledgeVerificationRequestDetailsUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acknowledgeVerificationRequestDetailsUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acknowledgeVerificationRequestDetailsCalled: Bool {
|
||||||
|
return acknowledgeVerificationRequestDetailsCallsCount > 0
|
||||||
|
}
|
||||||
|
var acknowledgeVerificationRequestDetailsReceivedDetails: SessionVerificationRequestDetails?
|
||||||
|
var acknowledgeVerificationRequestDetailsReceivedInvocations: [SessionVerificationRequestDetails] = []
|
||||||
|
|
||||||
|
var acknowledgeVerificationRequestDetailsUnderlyingReturnValue: Result<Void, SessionVerificationControllerProxyError>!
|
||||||
|
var acknowledgeVerificationRequestDetailsReturnValue: Result<Void, SessionVerificationControllerProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acknowledgeVerificationRequestDetailsUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Void, SessionVerificationControllerProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acknowledgeVerificationRequestDetailsUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acknowledgeVerificationRequestDetailsClosure: ((SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError>)?
|
||||||
|
|
||||||
|
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
acknowledgeVerificationRequestDetailsCallsCount += 1
|
||||||
|
acknowledgeVerificationRequestDetailsReceivedDetails = details
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.acknowledgeVerificationRequestDetailsReceivedInvocations.append(details)
|
||||||
|
}
|
||||||
|
if let acknowledgeVerificationRequestDetailsClosure = acknowledgeVerificationRequestDetailsClosure {
|
||||||
|
return await acknowledgeVerificationRequestDetailsClosure(details)
|
||||||
|
} else {
|
||||||
|
return acknowledgeVerificationRequestDetailsReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - acceptVerificationRequest
|
||||||
|
|
||||||
|
var acceptVerificationRequestUnderlyingCallsCount = 0
|
||||||
|
var acceptVerificationRequestCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acceptVerificationRequestCalled: Bool {
|
||||||
|
return acceptVerificationRequestCallsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var acceptVerificationRequestUnderlyingReturnValue: Result<Void, SessionVerificationControllerProxyError>!
|
||||||
|
var acceptVerificationRequestReturnValue: Result<Void, SessionVerificationControllerProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acceptVerificationRequestUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Void, SessionVerificationControllerProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acceptVerificationRequestUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acceptVerificationRequestUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acceptVerificationRequestUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acceptVerificationRequestClosure: (() async -> Result<Void, SessionVerificationControllerProxyError>)?
|
||||||
|
|
||||||
|
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
acceptVerificationRequestCallsCount += 1
|
||||||
|
if let acceptVerificationRequestClosure = acceptVerificationRequestClosure {
|
||||||
|
return await acceptVerificationRequestClosure()
|
||||||
|
} else {
|
||||||
|
return acceptVerificationRequestReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - requestVerification
|
//MARK: - requestVerification
|
||||||
|
|
||||||
var requestVerificationUnderlyingCallsCount = 0
|
var requestVerificationUnderlyingCallsCount = 0
|
||||||
|
@ -16529,6 +16529,92 @@ open class SessionVerificationControllerSDKMock: MatrixRustSDK.SessionVerificati
|
|||||||
|
|
||||||
fileprivate var pointer: UnsafeMutableRawPointer!
|
fileprivate var pointer: UnsafeMutableRawPointer!
|
||||||
|
|
||||||
|
//MARK: - acceptVerificationRequest
|
||||||
|
|
||||||
|
open var acceptVerificationRequestThrowableError: Error?
|
||||||
|
var acceptVerificationRequestUnderlyingCallsCount = 0
|
||||||
|
open var acceptVerificationRequestCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var acceptVerificationRequestCalled: Bool {
|
||||||
|
return acceptVerificationRequestCallsCount > 0
|
||||||
|
}
|
||||||
|
open var acceptVerificationRequestClosure: (() async throws -> Void)?
|
||||||
|
|
||||||
|
open override func acceptVerificationRequest() async throws {
|
||||||
|
if let error = acceptVerificationRequestThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
acceptVerificationRequestCallsCount += 1
|
||||||
|
try await acceptVerificationRequestClosure?()
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - acknowledgeVerificationRequest
|
||||||
|
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdThrowableError: Error?
|
||||||
|
var acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount = 0
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdCalled: Bool {
|
||||||
|
return acknowledgeVerificationRequestSenderIdFlowIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdReceivedArguments: (senderId: String, flowId: String)?
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdReceivedInvocations: [(senderId: String, flowId: String)] = []
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdClosure: ((String, String) async throws -> Void)?
|
||||||
|
|
||||||
|
open override func acknowledgeVerificationRequest(senderId: String, flowId: String) async throws {
|
||||||
|
if let error = acknowledgeVerificationRequestSenderIdFlowIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdCallsCount += 1
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdReceivedArguments = (senderId: senderId, flowId: flowId)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.acknowledgeVerificationRequestSenderIdFlowIdReceivedInvocations.append((senderId: senderId, flowId: flowId))
|
||||||
|
}
|
||||||
|
try await acknowledgeVerificationRequestSenderIdFlowIdClosure?(senderId, flowId)
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - approveVerification
|
//MARK: - approveVerification
|
||||||
|
|
||||||
open var approveVerificationThrowableError: Error?
|
open var approveVerificationThrowableError: Error?
|
||||||
@ -16649,75 +16735,6 @@ open class SessionVerificationControllerSDKMock: MatrixRustSDK.SessionVerificati
|
|||||||
try await declineVerificationClosure?()
|
try await declineVerificationClosure?()
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - isVerified
|
|
||||||
|
|
||||||
open var isVerifiedThrowableError: Error?
|
|
||||||
var isVerifiedUnderlyingCallsCount = 0
|
|
||||||
open var isVerifiedCallsCount: Int {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return isVerifiedUnderlyingCallsCount
|
|
||||||
} else {
|
|
||||||
var returnValue: Int? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = isVerifiedUnderlyingCallsCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
isVerifiedUnderlyingCallsCount = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
isVerifiedUnderlyingCallsCount = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var isVerifiedCalled: Bool {
|
|
||||||
return isVerifiedCallsCount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var isVerifiedUnderlyingReturnValue: Bool!
|
|
||||||
open var isVerifiedReturnValue: Bool! {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return isVerifiedUnderlyingReturnValue
|
|
||||||
} else {
|
|
||||||
var returnValue: Bool? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = isVerifiedUnderlyingReturnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
isVerifiedUnderlyingReturnValue = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
isVerifiedUnderlyingReturnValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var isVerifiedClosure: (() async throws -> Bool)?
|
|
||||||
|
|
||||||
open override func isVerified() async throws -> Bool {
|
|
||||||
if let error = isVerifiedThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
isVerifiedCallsCount += 1
|
|
||||||
if let isVerifiedClosure = isVerifiedClosure {
|
|
||||||
return try await isVerifiedClosure()
|
|
||||||
} else {
|
|
||||||
return isVerifiedReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: - requestVerification
|
//MARK: - requestVerification
|
||||||
|
|
||||||
open var requestVerificationThrowableError: Error?
|
open var requestVerificationThrowableError: Error?
|
||||||
|
@ -16,16 +16,18 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
SessionVerificationEmoji(symbol: "🏁", description: "Flag"),
|
SessionVerificationEmoji(symbol: "🏁", description: "Flag"),
|
||||||
SessionVerificationEmoji(symbol: "🌏", description: "Globe")]
|
SessionVerificationEmoji(symbol: "🌏", description: "Globe")]
|
||||||
|
|
||||||
static func configureMock(callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> = .init(),
|
static func configureMock(actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> = .init(),
|
||||||
isVerified: Bool = false,
|
isVerified: Bool = false,
|
||||||
requestDelay: Duration = .seconds(1)) -> SessionVerificationControllerProxyMock {
|
requestDelay: Duration = .seconds(1)) -> SessionVerificationControllerProxyMock {
|
||||||
let mock = SessionVerificationControllerProxyMock()
|
let mock = SessionVerificationControllerProxyMock()
|
||||||
mock.underlyingCallbacks = callbacks
|
mock.underlyingActions = actions
|
||||||
|
|
||||||
|
mock.acknowledgeVerificationRequestDetailsReturnValue = .success(())
|
||||||
|
|
||||||
mock.requestVerificationClosure = { [unowned mock] in
|
mock.requestVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.acceptedVerificationRequest)
|
mock.actions.send(.acceptedVerificationRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
@ -34,11 +36,11 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.startSasVerificationClosure = { [unowned mock] in
|
mock.startSasVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.startedSasVerification)
|
mock.actions.send(.startedSasVerification)
|
||||||
|
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.receivedVerificationData(emojis))
|
mock.actions.send(.receivedVerificationData(emojis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.approveVerificationClosure = { [unowned mock] in
|
mock.approveVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.finished)
|
mock.actions.send(.finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
@ -57,7 +59,7 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.declineVerificationClosure = { [unowned mock] in
|
mock.declineVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.cancelled)
|
mock.actions.send(.cancelled)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
@ -66,7 +68,7 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.cancelVerificationClosure = { [unowned mock] in
|
mock.cancelVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.cancelled)
|
mock.actions.send(.cancelled)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
|
@ -184,6 +184,8 @@ enum A11yIdentifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct SessionVerificationScreen {
|
struct SessionVerificationScreen {
|
||||||
|
let acceptVerificationRequest = "session_verification-accept_verification_request"
|
||||||
|
let ignoreVerificationRequest = "session_verification-ignore_verification_request"
|
||||||
let requestVerification = "session_verification-request_verification"
|
let requestVerification = "session_verification-request_verification"
|
||||||
let startSasVerification = "session_verification-start_sas_verification"
|
let startSasVerification = "session_verification-start_sas_verification"
|
||||||
let acceptChallenge = "session_verification-accept_challenge"
|
let acceptChallenge = "session_verification-accept_challenge"
|
||||||
|
@ -6,14 +6,21 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import MatrixRustSDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum SessionVerificationScreenCoordinatorAction {
|
enum SessionVerificationScreenCoordinatorAction {
|
||||||
case done
|
case done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SessionVerificationScreenFlow {
|
||||||
|
case initiator
|
||||||
|
case responder(details: SessionVerificationRequestDetails)
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionVerificationScreenCoordinatorParameters {
|
struct SessionVerificationScreenCoordinatorParameters {
|
||||||
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||||
|
let flow: SessionVerificationScreenFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||||
@ -27,7 +34,8 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(parameters: SessionVerificationScreenCoordinatorParameters) {
|
init(parameters: SessionVerificationScreenCoordinatorParameters) {
|
||||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy)
|
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy,
|
||||||
|
flow: parameters.flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
@ -11,13 +11,61 @@ enum SessionVerificationScreenViewModelAction {
|
|||||||
case finished
|
case finished
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SessionVerificationScreenViewAction {
|
||||||
|
case acceptVerificationRequest
|
||||||
|
case ignoreVerificationRequest
|
||||||
|
case requestVerification
|
||||||
|
case startSasVerification
|
||||||
|
case restart
|
||||||
|
case accept
|
||||||
|
case decline
|
||||||
|
case done
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionVerificationScreenViewState: BindableState {
|
struct SessionVerificationScreenViewState: BindableState {
|
||||||
var verificationState: SessionVerificationScreenStateMachine.State = .initial
|
let flow: SessionVerificationScreenFlow
|
||||||
|
var verificationState: SessionVerificationScreenStateMachine.State
|
||||||
|
|
||||||
|
var headerImageName: String {
|
||||||
|
switch verificationState {
|
||||||
|
case .initial:
|
||||||
|
return "lock"
|
||||||
|
case .acceptingVerificationRequest:
|
||||||
|
return "hourglass"
|
||||||
|
case .requestingVerification:
|
||||||
|
return "hourglass"
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
return "face.smiling"
|
||||||
|
case .startingSasVerification:
|
||||||
|
return "hourglass"
|
||||||
|
case .sasVerificationStarted:
|
||||||
|
return "hourglass"
|
||||||
|
case .cancelling:
|
||||||
|
return "hourglass"
|
||||||
|
case .acceptingChallenge:
|
||||||
|
return "hourglass"
|
||||||
|
case .decliningChallenge:
|
||||||
|
return "hourglass"
|
||||||
|
case .showingChallenge:
|
||||||
|
return "face.smiling"
|
||||||
|
case .verified:
|
||||||
|
return "checkmark.shield"
|
||||||
|
case .cancelled:
|
||||||
|
return "exclamationmark.shield"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var title: String? {
|
var title: String? {
|
||||||
switch verificationState {
|
switch verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
return L10n.screenSessionVerificationOpenExistingSessionTitle
|
switch flow {
|
||||||
|
case .initiator:
|
||||||
|
return L10n.screenSessionVerificationOpenExistingSessionTitle
|
||||||
|
case .responder:
|
||||||
|
return L10n.screenSessionVerificationRequestTitle
|
||||||
|
}
|
||||||
|
case .acceptingVerificationRequest:
|
||||||
|
return L10n.screenSessionVerificationRequestTitle
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
return L10n.screenSessionVerificationWaitingToAcceptTitle
|
return L10n.screenSessionVerificationWaitingToAcceptTitle
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
@ -31,13 +79,13 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
case .acceptingChallenge:
|
case .acceptingChallenge:
|
||||||
return L10n.screenSessionVerificationCompareEmojisTitle
|
return L10n.screenSessionVerificationCompareEmojisTitle
|
||||||
case .decliningChallenge:
|
case .decliningChallenge:
|
||||||
return nil
|
return L10n.screenSessionVerificationCompareEmojisTitle
|
||||||
case .verified:
|
case .verified:
|
||||||
return L10n.commonVerificationComplete
|
return L10n.commonVerificationComplete
|
||||||
case .cancelling:
|
case .cancelling:
|
||||||
return nil
|
return nil
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
return L10n.commonVerificationCancelled
|
return L10n.commonVerificationFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +96,14 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
var message: String {
|
var message: String {
|
||||||
switch verificationState {
|
switch verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
return L10n.screenSessionVerificationOpenExistingSessionSubtitle
|
switch flow {
|
||||||
|
case .initiator:
|
||||||
|
return L10n.screenSessionVerificationOpenExistingSessionSubtitle
|
||||||
|
case .responder:
|
||||||
|
return L10n.screenSessionVerificationRequestSubtitle
|
||||||
|
}
|
||||||
|
case .acceptingVerificationRequest:
|
||||||
|
return L10n.screenSessionVerificationRequestSubtitle
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
return L10n.screenSessionVerificationWaitingToAcceptSubtitle
|
return L10n.screenSessionVerificationWaitingToAcceptSubtitle
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
@ -60,7 +115,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
case .acceptingChallenge:
|
case .acceptingChallenge:
|
||||||
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||||
case .decliningChallenge:
|
case .decliningChallenge:
|
||||||
return L10n.commonWaiting
|
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||||
case .cancelling:
|
case .cancelling:
|
||||||
return L10n.commonWaiting
|
return L10n.commonWaiting
|
||||||
case .showingChallenge:
|
case .showingChallenge:
|
||||||
@ -68,15 +123,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
case .verified:
|
case .verified:
|
||||||
return L10n.screenSessionVerificationCompleteSubtitle
|
return L10n.screenSessionVerificationCompleteSubtitle
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
return L10n.screenSessionVerificationCancelledSubtitle
|
return L10n.screenSessionVerificationFailedSubtitle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SessionVerificationScreenViewAction {
|
|
||||||
case requestVerification
|
|
||||||
case startSasVerification
|
|
||||||
case restart
|
|
||||||
case accept
|
|
||||||
case decline
|
|
||||||
}
|
|
||||||
|
@ -13,6 +13,8 @@ class SessionVerificationScreenStateMachine {
|
|||||||
enum State: StateType {
|
enum State: StateType {
|
||||||
/// The initial state, before verification started
|
/// The initial state, before verification started
|
||||||
case initial
|
case initial
|
||||||
|
/// Accepting the remote verification request
|
||||||
|
case acceptingVerificationRequest
|
||||||
/// Waiting for verification acceptance
|
/// Waiting for verification acceptance
|
||||||
case requestingVerification
|
case requestingVerification
|
||||||
/// Verification request accepted. Waiting for start
|
/// Verification request accepted. Waiting for start
|
||||||
@ -37,6 +39,8 @@ class SessionVerificationScreenStateMachine {
|
|||||||
|
|
||||||
/// Events that can be triggered on the SessionVerification state machine
|
/// Events that can be triggered on the SessionVerification state machine
|
||||||
enum Event: EventType {
|
enum Event: EventType {
|
||||||
|
/// Accept the remote verification request
|
||||||
|
case acceptVerificationRequest
|
||||||
/// Request verification
|
/// Request verification
|
||||||
case requestVerification
|
case requestVerification
|
||||||
/// The current verification request has been accepted
|
/// The current verification request has been accepted
|
||||||
@ -69,16 +73,23 @@ class SessionVerificationScreenStateMachine {
|
|||||||
stateMachine.state
|
stateMachine.state
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init(state: State) {
|
||||||
stateMachine = StateMachine(state: .initial)
|
stateMachine = StateMachine(state: state)
|
||||||
configure()
|
configure()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configure() {
|
private func configure() {
|
||||||
|
stateMachine.addRoutes(event: .acceptVerificationRequest, transitions: [.initial => .acceptingVerificationRequest])
|
||||||
stateMachine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
stateMachine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
||||||
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.requestingVerification => .verificationRequestAccepted])
|
|
||||||
|
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.acceptingVerificationRequest => .verificationRequestAccepted,
|
||||||
|
.requestingVerification => .verificationRequestAccepted])
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .startSasVerification, transitions: [.verificationRequestAccepted => .startingSasVerification])
|
stateMachine.addRoutes(event: .startSasVerification, transitions: [.verificationRequestAccepted => .startingSasVerification])
|
||||||
stateMachine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial])
|
|
||||||
|
stateMachine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial,
|
||||||
|
.acceptingVerificationRequest => .initial])
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
|
stateMachine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
|
||||||
|
|
||||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||||
|
@ -12,6 +12,7 @@ typealias SessionVerificationViewModelType = StateStoreViewModel<SessionVerifica
|
|||||||
|
|
||||||
class SessionVerificationScreenViewModel: SessionVerificationViewModelType, SessionVerificationScreenViewModelProtocol {
|
class SessionVerificationScreenViewModel: SessionVerificationViewModelType, SessionVerificationScreenViewModelProtocol {
|
||||||
private let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
private let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||||
|
private let flow: SessionVerificationScreenFlow
|
||||||
|
|
||||||
private var stateMachine: SessionVerificationScreenStateMachine
|
private var stateMachine: SessionVerificationScreenStateMachine
|
||||||
|
|
||||||
@ -22,21 +23,25 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
||||||
|
flow: SessionVerificationScreenFlow,
|
||||||
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
|
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
|
||||||
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
|
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
|
||||||
|
self.flow = flow
|
||||||
|
|
||||||
stateMachine = SessionVerificationScreenStateMachine()
|
stateMachine = SessionVerificationScreenStateMachine(state: verificationState)
|
||||||
|
|
||||||
super.init(initialViewState: .init(verificationState: verificationState))
|
super.init(initialViewState: .init(flow: flow, verificationState: verificationState))
|
||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
|
|
||||||
sessionVerificationControllerProxy.callbacks
|
sessionVerificationControllerProxy.actions
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] callback in
|
.sink { [weak self] callback in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch callback {
|
switch callback {
|
||||||
|
case .receivedVerificationRequest:
|
||||||
|
break // Incoming verification requests are handled on the higher levels
|
||||||
case .acceptedVerificationRequest:
|
case .acceptedVerificationRequest:
|
||||||
self.stateMachine.processEvent(.didAcceptVerificationRequest)
|
self.stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
case .startedSasVerification:
|
case .startedSasVerification:
|
||||||
@ -57,10 +62,20 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
if case .responder(let details) = flow {
|
||||||
|
Task {
|
||||||
|
await self.sessionVerificationControllerProxy.acknowledgeVerificationRequest(details: details)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: SessionVerificationScreenViewAction) {
|
override func process(viewAction: SessionVerificationScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
|
case .acceptVerificationRequest:
|
||||||
|
stateMachine.processEvent(.acceptVerificationRequest)
|
||||||
|
case .ignoreVerificationRequest:
|
||||||
|
actionsSubject.send(.finished)
|
||||||
case .requestVerification:
|
case .requestVerification:
|
||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
case .startSasVerification:
|
case .startSasVerification:
|
||||||
@ -71,13 +86,16 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
stateMachine.processEvent(.acceptChallenge)
|
stateMachine.processEvent(.acceptChallenge)
|
||||||
case .decline:
|
case .decline:
|
||||||
stateMachine.processEvent(.declineChallenge)
|
stateMachine.processEvent(.declineChallenge)
|
||||||
|
case .done:
|
||||||
|
actionsSubject.send(.finished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
let uncancellableStates: [SessionVerificationScreenStateMachine.State] = [.initial, .verified, .cancelled]
|
switch stateMachine.state {
|
||||||
|
case .initial, .verified, .cancelled: // non-cancellable states
|
||||||
if !uncancellableStates.contains(stateMachine.state) {
|
return
|
||||||
|
default:
|
||||||
stateMachine.processEvent(.cancel)
|
stateMachine.processEvent(.cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,6 +109,8 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
state.verificationState = context.toState
|
state.verificationState = context.toState
|
||||||
|
|
||||||
switch (context.fromState, context.event, context.toState) {
|
switch (context.fromState, context.event, context.toState) {
|
||||||
|
case (.initial, .acceptVerificationRequest, .acceptingVerificationRequest):
|
||||||
|
acceptVerificationRequest()
|
||||||
case (.initial, .requestVerification, .requestingVerification):
|
case (.initial, .requestVerification, .requestingVerification):
|
||||||
requestVerification()
|
requestVerification()
|
||||||
case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification):
|
case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification):
|
||||||
@ -103,6 +123,10 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
cancelVerification()
|
cancelVerification()
|
||||||
case (_, _, .verified):
|
case (_, _, .verified):
|
||||||
actionsSubject.send(.finished)
|
actionsSubject.send(.finished)
|
||||||
|
case (.initial, _, .cancelled):
|
||||||
|
if case .responder = flow {
|
||||||
|
actionsSubject.send(.finished)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -113,6 +137,21 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func acceptVerificationRequest() {
|
||||||
|
Task {
|
||||||
|
guard case .responder = flow else {
|
||||||
|
fatalError("Incorrect API usage.")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await sessionVerificationControllerProxy.acceptVerificationRequest() {
|
||||||
|
case .success:
|
||||||
|
stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
|
case .failure:
|
||||||
|
stateMachine.processEvent(.didFail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func requestVerification() {
|
private func requestVerification() {
|
||||||
Task {
|
Task {
|
||||||
switch await sessionVerificationControllerProxy.requestVerification() {
|
switch await sessionVerificationControllerProxy.requestVerification() {
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import MatrixRustSDK
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SessionVerificationRequestDetailsView: View {
|
||||||
|
@ScaledMetric private var iconSize = 30.0
|
||||||
|
private let outerShape = RoundedRectangle(cornerRadius: 8)
|
||||||
|
|
||||||
|
let details: SessionVerificationRequestDetails
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
CompoundIcon(\.devices)
|
||||||
|
.frame(width: iconSize, height: iconSize)
|
||||||
|
.foregroundColor(.compound.iconSecondary)
|
||||||
|
.padding(6)
|
||||||
|
.background(.compound.bgSubtleSecondary)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
|
|
||||||
|
Text(details.displayName ?? details.senderID)
|
||||||
|
.font(.compound.bodyMDSemibold)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 40) {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text(L10n.screenSessionVerificationRequestDetailsTimestamp)
|
||||||
|
.font(.compound.bodySM)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
Text(details.firstSeenDate.formattedMinimal())
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text(L10n.commonDeviceId)
|
||||||
|
.font(.compound.bodySM)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
Text(details.deviceID)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(24)
|
||||||
|
.clipShape(outerShape)
|
||||||
|
.overlay {
|
||||||
|
outerShape
|
||||||
|
.inset(by: 0.25)
|
||||||
|
.stroke(.compound.borderDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(L10n.screenSessionVerificationRequestFooter)
|
||||||
|
.font(.compound.bodyMDSemibold)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SessionVerificationRequestDetailsView_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static var previews: some View {
|
||||||
|
let details = SessionVerificationRequestDetails(senderID: "@bob:matrix.org",
|
||||||
|
flowID: "123",
|
||||||
|
deviceID: "CODEMISTAKE",
|
||||||
|
displayName: "Bob's Element X iOS",
|
||||||
|
firstSeenDate: .init(timeIntervalSince1970: 0))
|
||||||
|
|
||||||
|
SessionVerificationRequestDetailsView(details: details)
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@
|
|||||||
// Please see LICENSE in the repository root for full details.
|
// Please see LICENSE in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import MatrixRustSDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SessionVerificationScreen: View {
|
struct SessionVerificationScreen: View {
|
||||||
@ -14,7 +16,6 @@ struct SessionVerificationScreen: View {
|
|||||||
FullscreenDialog {
|
FullscreenDialog {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 32) {
|
||||||
screenHeader
|
screenHeader
|
||||||
Spacer()
|
|
||||||
mainContent
|
mainContent
|
||||||
}
|
}
|
||||||
} bottomContent: {
|
} bottomContent: {
|
||||||
@ -27,33 +28,6 @@ struct SessionVerificationScreen: View {
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private var headerImageName: String {
|
|
||||||
switch context.viewState.verificationState {
|
|
||||||
case .initial:
|
|
||||||
return "lock"
|
|
||||||
case .cancelled:
|
|
||||||
return "exclamationmark.shield"
|
|
||||||
case .requestingVerification:
|
|
||||||
return "hourglass"
|
|
||||||
case .verificationRequestAccepted:
|
|
||||||
return "face.smiling"
|
|
||||||
case .startingSasVerification:
|
|
||||||
return "hourglass"
|
|
||||||
case .sasVerificationStarted:
|
|
||||||
return "hourglass"
|
|
||||||
case .cancelling:
|
|
||||||
return "hourglass"
|
|
||||||
case .acceptingChallenge:
|
|
||||||
return "hourglass"
|
|
||||||
case .decliningChallenge:
|
|
||||||
return "hourglass"
|
|
||||||
case .showingChallenge:
|
|
||||||
return "face.smiling"
|
|
||||||
case .verified:
|
|
||||||
return "checkmark.shield"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var screenHeader: some View {
|
private var screenHeader: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@ -61,11 +35,11 @@ struct SessionVerificationScreen: View {
|
|||||||
BigIcon(icon: \.lockSolid)
|
BigIcon(icon: \.lockSolid)
|
||||||
.padding(.bottom, 16)
|
.padding(.bottom, 16)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: headerImageName)
|
Image(systemName: context.viewState.headerImageName)
|
||||||
.bigIcon()
|
.bigIcon()
|
||||||
.padding(.bottom, 16)
|
.padding(.bottom, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(context.viewState.title ?? "")
|
Text(context.viewState.title ?? "")
|
||||||
.font(.compound.headingMDBold)
|
.font(.compound.headingMDBold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@ -83,18 +57,17 @@ struct SessionVerificationScreen: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .showingChallenge(let emojis):
|
case .initial:
|
||||||
|
switch context.viewState.flow {
|
||||||
|
case .responder(let details):
|
||||||
|
SessionVerificationRequestDetailsView(details: details)
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
case .showingChallenge(let emojis), .acceptingChallenge(let emojis), .decliningChallenge(let emojis):
|
||||||
emojisPanel(with: emojis)
|
emojisPanel(with: emojis)
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
||||||
case .acceptingChallenge(let emojis):
|
|
||||||
emojisPanel(with: emojis)
|
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
|
||||||
case .requestingVerification:
|
|
||||||
ProgressView()
|
|
||||||
.tint(.compound.textSecondary)
|
|
||||||
.scaleEffect(2)
|
|
||||||
default:
|
default:
|
||||||
// In All other cases, we just want an empty view
|
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,18 +92,41 @@ struct SessionVerificationScreen: View {
|
|||||||
private var actionButtons: some View {
|
private var actionButtons: some View {
|
||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
VStack(spacing: 32) {
|
switch context.viewState.flow {
|
||||||
|
case .initiator:
|
||||||
Button(L10n.actionStartVerification) {
|
Button(L10n.actionStartVerification) {
|
||||||
context.send(viewAction: .requestVerification)
|
context.send(viewAction: .requestVerification)
|
||||||
}
|
}
|
||||||
.buttonStyle(.compound(.primary))
|
.buttonStyle(.compound(.primary))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
|
||||||
|
case .responder:
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Button(L10n.actionStart) {
|
||||||
|
context.send(viewAction: .acceptVerificationRequest)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptVerificationRequest)
|
||||||
|
|
||||||
|
Button(L10n.actionIgnore) {
|
||||||
|
context.send(viewAction: .ignoreVerificationRequest)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.plain))
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.ignoreVerificationRequest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
Button(L10n.actionRetry) {
|
switch context.viewState.flow {
|
||||||
context.send(viewAction: .restart)
|
case .initiator:
|
||||||
|
Button(L10n.actionRetry) {
|
||||||
|
context.send(viewAction: .restart)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
case .responder:
|
||||||
|
Button(L10n.actionDone) {
|
||||||
|
context.send(viewAction: .done)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
}
|
}
|
||||||
.buttonStyle(.compound(.primary))
|
|
||||||
|
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
Button(L10n.actionStart) {
|
Button(L10n.actionStart) {
|
||||||
@ -150,31 +146,15 @@ struct SessionVerificationScreen: View {
|
|||||||
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
||||||
context.send(viewAction: .decline)
|
context.send(viewAction: .decline)
|
||||||
}
|
}
|
||||||
.font(.compound.bodyLGSemibold)
|
.buttonStyle(.compound(.plain))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .acceptingChallenge:
|
case .acceptingVerificationRequest, .acceptingChallenge, .decliningChallenge, .requestingVerification:
|
||||||
VStack(spacing: 32) {
|
Button(L10n.screenIdentityWaitingOnOtherDevice) { }
|
||||||
Button { context.send(viewAction: .accept) } label: {
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
ProgressView()
|
|
||||||
.tint(.compound.textOnSolidPrimary)
|
|
||||||
Text(L10n.screenSessionVerificationTheyMatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(.compound(.primary))
|
.buttonStyle(.compound(.primary))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
|
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
|
||||||
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
|
||||||
context.send(viewAction: .decline)
|
|
||||||
}
|
|
||||||
.font(.compound.bodyLGSemibold)
|
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
|
||||||
.disabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@ -196,27 +176,50 @@ struct SessionVerificationScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
|
||||||
|
|
||||||
struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
sessionVerificationScreen(state: .initial)
|
sessionVerificationScreen(state: .initial)
|
||||||
.previewDisplayName("Initial")
|
.previewDisplayName("Initial - Initiator")
|
||||||
|
|
||||||
|
let details = SessionVerificationRequestDetails(senderID: "@bob:matrix.org",
|
||||||
|
flowID: "123",
|
||||||
|
deviceID: "CODEMISTAKE",
|
||||||
|
displayName: "Bob's Element X iOS",
|
||||||
|
firstSeenDate: .init(timeIntervalSince1970: 0))
|
||||||
|
sessionVerificationScreen(state: .initial, flow: .responder(details: details))
|
||||||
|
.previewDisplayName("Initial - Responder")
|
||||||
|
|
||||||
|
sessionVerificationScreen(state: .acceptingVerificationRequest)
|
||||||
|
.previewDisplayName("Accepting Verification Request")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .requestingVerification)
|
sessionVerificationScreen(state: .requestingVerification)
|
||||||
.previewDisplayName("Requesting Verification")
|
.previewDisplayName("Requesting Verification")
|
||||||
sessionVerificationScreen(state: .verificationRequestAccepted)
|
sessionVerificationScreen(state: .verificationRequestAccepted)
|
||||||
.previewDisplayName("Request Accepted")
|
.previewDisplayName("Request Accepted")
|
||||||
sessionVerificationScreen(state: .cancelled)
|
|
||||||
.previewDisplayName("Cancelled")
|
sessionVerificationScreen(state: .startingSasVerification)
|
||||||
|
.previewDisplayName("Starting SAS Verification")
|
||||||
|
sessionVerificationScreen(state: .sasVerificationStarted)
|
||||||
|
.previewDisplayName("SAS Verification started")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
sessionVerificationScreen(state: .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||||
.previewDisplayName("Showing Challenge")
|
.previewDisplayName("Showing Challenge")
|
||||||
|
sessionVerificationScreen(state: .acceptingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||||
|
.previewDisplayName("Accepting Challenge")
|
||||||
|
sessionVerificationScreen(state: .decliningChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||||
|
.previewDisplayName("Declining Challenge")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .verified)
|
sessionVerificationScreen(state: .verified)
|
||||||
.previewDisplayName("Verified")
|
.previewDisplayName("Verified")
|
||||||
|
|
||||||
|
sessionVerificationScreen(state: .cancelled)
|
||||||
|
.previewDisplayName("Cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State) -> some View {
|
static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State,
|
||||||
|
flow: SessionVerificationScreenFlow = .initiator) -> some View {
|
||||||
let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(),
|
let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(),
|
||||||
|
flow: flow,
|
||||||
verificationState: state)
|
verificationState: state)
|
||||||
|
|
||||||
return SessionVerificationScreen(context: viewModel.context)
|
return SessionVerificationScreen(context: viewModel.context)
|
||||||
|
@ -91,6 +91,7 @@ class TimelineInteractionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
|
@ -50,6 +50,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
let secureBackupController: SecureBackupControllerProtocol
|
let secureBackupController: SecureBackupControllerProtocol
|
||||||
|
|
||||||
|
private(set) var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||||
|
|
||||||
private static var roomCreationPowerLevelOverrides: PowerLevels {
|
private static var roomCreationPowerLevelOverrides: PowerLevels {
|
||||||
.init(usersDefault: nil,
|
.init(usersDefault: nil,
|
||||||
eventsDefault: nil,
|
eventsDefault: nil,
|
||||||
@ -157,7 +159,16 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
updateVerificationState(client.encryption().verificationState())
|
updateVerificationState(client.encryption().verificationState())
|
||||||
|
|
||||||
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
|
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
|
||||||
self?.updateVerificationState(verificationState)
|
guard let self else { return }
|
||||||
|
|
||||||
|
updateVerificationState(verificationState)
|
||||||
|
|
||||||
|
// The session verification controller requires the user's identity which
|
||||||
|
// isn't available before a keys query response. Use the verification
|
||||||
|
// state updates as an aproximation for when that happens.
|
||||||
|
Task {
|
||||||
|
await self.buildSessionVerificationControllerProxyIfPossible(verificationState: verificationState)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sendQueueListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SendQueueRoomErrorListenerProxy { [weak self] roomID, error in
|
sendQueueListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SendQueueRoomErrorListenerProxy { [weak self] roomID, error in
|
||||||
@ -484,7 +495,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
|
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
|
||||||
do {
|
do {
|
||||||
let roomPreview = try await client.getRoomPreviewFromRoomId(roomId: identifier, viaServers: via)
|
let roomPreview = try await client.getRoomPreviewFromRoomId(roomId: identifier, viaServers: via)
|
||||||
return .success(.init(roomPreview))
|
let roomPreviewInfo = try roomPreview.info()
|
||||||
|
return .success(.init(roomPreviewInfo))
|
||||||
} catch let error as ClientError where error.code == .forbidden {
|
} catch let error as ClientError where error.code == .forbidden {
|
||||||
return .failure(.roomPreviewIsPrivate)
|
return .failure(.roomPreviewIsPrivate)
|
||||||
} catch {
|
} catch {
|
||||||
@ -561,16 +573,6 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
|
||||||
do {
|
|
||||||
let sessionVerificationController = try await client.getSessionVerificationController()
|
|
||||||
return .success(SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController))
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
|
|
||||||
return .failure(.sdkError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logout() async -> URL? {
|
func logout() async -> URL? {
|
||||||
do {
|
do {
|
||||||
return try await client.logout().flatMap(URL.init(string:))
|
return try await client.logout().flatMap(URL.init(string:))
|
||||||
@ -728,6 +730,19 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
verificationStateSubject.send(verificationState)
|
verificationStateSubject.send(verificationState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func buildSessionVerificationControllerProxyIfPossible(verificationState: VerificationState) async {
|
||||||
|
guard sessionVerificationController == nil, verificationState != .unknown else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let sessionVerificationController = try await client.getSessionVerificationController()
|
||||||
|
self.sessionVerificationController = SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func loadUserAvatarURLFromCache() {
|
private func loadUserAvatarURLFromCache() {
|
||||||
loadCachedAvatarURLTask = Task {
|
loadCachedAvatarURLTask = Task {
|
||||||
@ -1079,17 +1094,17 @@ private class SendQueueRoomErrorListenerProxy: SendQueueRoomErrorListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension RoomPreviewDetails {
|
private extension RoomPreviewDetails {
|
||||||
init(_ roomPreview: RoomPreview) {
|
init(_ roomPreviewInfo: RoomPreviewInfo) {
|
||||||
self = RoomPreviewDetails(roomID: roomPreview.roomId,
|
self = RoomPreviewDetails(roomID: roomPreviewInfo.roomId,
|
||||||
name: roomPreview.name,
|
name: roomPreviewInfo.name,
|
||||||
canonicalAlias: roomPreview.canonicalAlias,
|
canonicalAlias: roomPreviewInfo.canonicalAlias,
|
||||||
topic: roomPreview.topic,
|
topic: roomPreviewInfo.topic,
|
||||||
avatarURL: roomPreview.avatarUrl.flatMap(URL.init(string:)),
|
avatarURL: roomPreviewInfo.avatarUrl.flatMap(URL.init(string:)),
|
||||||
memberCount: UInt(roomPreview.numJoinedMembers),
|
memberCount: UInt(roomPreviewInfo.numJoinedMembers),
|
||||||
isHistoryWorldReadable: roomPreview.isHistoryWorldReadable,
|
isHistoryWorldReadable: roomPreviewInfo.isHistoryWorldReadable,
|
||||||
isJoined: roomPreview.isJoined,
|
isJoined: roomPreviewInfo.membership == .joined,
|
||||||
isInvited: roomPreview.isInvited,
|
isInvited: roomPreviewInfo.membership == .invited,
|
||||||
isPublic: roomPreview.isPublic,
|
isPublic: roomPreviewInfo.joinRule == .public,
|
||||||
canKnock: roomPreview.canKnock)
|
canKnock: roomPreviewInfo.joinRule == .knock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
var secureBackupController: SecureBackupControllerProtocol { get }
|
var secureBackupController: SecureBackupControllerProtocol { get }
|
||||||
|
|
||||||
|
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
|
||||||
|
|
||||||
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
||||||
|
|
||||||
func startSync()
|
func startSync()
|
||||||
@ -155,8 +157,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError>
|
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
func removeUserAvatar() async -> Result<Void, ClientProxyError>
|
func removeUserAvatar() async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
|
|
||||||
|
|
||||||
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
|
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
|
@ -246,10 +246,8 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
// This gets called for no reason on simulators, where CallKit
|
// This gets called for no reason on simulators, where CallKit
|
||||||
// isn't even supported. Ignore
|
// isn't even supported, ignore it.
|
||||||
return
|
#else
|
||||||
#endif
|
|
||||||
|
|
||||||
if let ongoingCallID {
|
if let ongoingCallID {
|
||||||
actionsSubject.send(.endCall(roomID: ongoingCallID.roomID))
|
actionsSubject.send(.endCall(roomID: ongoingCallID.roomID))
|
||||||
}
|
}
|
||||||
@ -257,6 +255,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
tearDownCallSession(sendEndCallAction: false)
|
tearDownCallSession(sendEndCallAction: false)
|
||||||
|
|
||||||
action.fulfill()
|
action.fulfill()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
@ -18,6 +18,10 @@ private class WeakSessionVerificationControllerProxy: SessionVerificationControl
|
|||||||
|
|
||||||
// MARK: - SessionVerificationControllerDelegate
|
// MARK: - SessionVerificationControllerDelegate
|
||||||
|
|
||||||
|
func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||||
|
proxy?.didReceiveVerificationRequest(details: details)
|
||||||
|
}
|
||||||
|
|
||||||
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
|
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
|
||||||
switch data {
|
switch data {
|
||||||
// We can handle only emojis for now
|
// We can handle only emojis for now
|
||||||
@ -54,86 +58,142 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
|
|||||||
|
|
||||||
init(sessionVerificationController: SessionVerificationController) {
|
init(sessionVerificationController: SessionVerificationController) {
|
||||||
self.sessionVerificationController = sessionVerificationController
|
self.sessionVerificationController = sessionVerificationController
|
||||||
|
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
sessionVerificationController.setDelegate(delegate: nil)
|
sessionVerificationController.setDelegate(delegate: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<SessionVerificationControllerProxyCallback, Never>()
|
let actions = PassthroughSubject<SessionVerificationControllerProxyAction, Never>()
|
||||||
|
|
||||||
|
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Acknowledging verification request")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await sessionVerificationController.acknowledgeVerificationRequest(senderId: details.senderID, flowId: details.flowID)
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||||
|
return .failure(.failedAcknowledgingVerificationRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Accepting verification request")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await sessionVerificationController.acceptVerificationRequest()
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||||
|
return .failure(.failedAcceptingVerificationRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
MXLog.info("Requesting session verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.requestVerification()
|
try await sessionVerificationController.requestVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||||
return .failure(.failedRequestingVerification)
|
return .failure(.failedRequestingVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Starting SAS verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.startSasVerification()
|
try await sessionVerificationController.startSasVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed starting SAS verification with error: \(error)")
|
||||||
return .failure(.failedStartingSasVerification)
|
return .failure(.failedStartingSasVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Approving verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.approveVerification()
|
try await sessionVerificationController.approveVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed approving verification with error: \(error)")
|
||||||
return .failure(.failedApprovingVerification)
|
return .failure(.failedApprovingVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Declining verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.declineVerification()
|
try await sessionVerificationController.declineVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed declining verification with error: \(error)")
|
||||||
return .failure(.failedDecliningVerification)
|
return .failure(.failedDecliningVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Cancelling verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.cancelVerification()
|
try await sessionVerificationController.cancelVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed cancelling verification with error: \(error)")
|
||||||
return .failure(.failedCancellingVerification)
|
return .failure(.failedCancellingVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
fileprivate func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||||
|
MXLog.info("Received verification request \(details)")
|
||||||
|
|
||||||
|
let details = SessionVerificationRequestDetails(senderID: details.senderId,
|
||||||
|
flowID: details.flowId,
|
||||||
|
deviceID: details.deviceId,
|
||||||
|
displayName: details.displayName,
|
||||||
|
firstSeenDate: Date(timeIntervalSince1970: TimeInterval(details.firstSeenTimestamp / 1000)))
|
||||||
|
|
||||||
|
actions.send(.receivedVerificationRequest(details: details))
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func didAcceptVerificationRequest() {
|
fileprivate func didAcceptVerificationRequest() {
|
||||||
callbacks.send(.acceptedVerificationRequest)
|
MXLog.info("Accepted verification request")
|
||||||
|
|
||||||
|
actions.send(.acceptedVerificationRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didStartSasVerification() {
|
fileprivate func didStartSasVerification() {
|
||||||
callbacks.send(.startedSasVerification)
|
MXLog.info("Started SAS verification")
|
||||||
|
|
||||||
|
actions.send(.startedSasVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
||||||
callbacks.send(.receivedVerificationData(data.map { emoji in
|
MXLog.info("Received verification data")
|
||||||
|
|
||||||
|
actions.send(.receivedVerificationData(data.map { emoji in
|
||||||
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didFail() {
|
fileprivate func didFail() {
|
||||||
callbacks.send(.failed)
|
actions.send(.failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didFinish() {
|
fileprivate func didFinish() {
|
||||||
callbacks.send(.finished)
|
actions.send(.finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didCancel() {
|
fileprivate func didCancel() {
|
||||||
callbacks.send(.cancelled)
|
actions.send(.cancelled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,11 @@
|
|||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
enum SessionVerificationControllerProxyError: Error {
|
enum SessionVerificationControllerProxyError: Error {
|
||||||
|
case failedAcknowledgingVerificationRequest
|
||||||
|
case failedAcceptingVerificationRequest
|
||||||
case failedRequestingVerification
|
case failedRequestingVerification
|
||||||
case failedStartingSasVerification
|
case failedStartingSasVerification
|
||||||
case failedApprovingVerification
|
case failedApprovingVerification
|
||||||
@ -16,7 +19,8 @@ enum SessionVerificationControllerProxyError: Error {
|
|||||||
case failedCancellingVerification
|
case failedCancellingVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SessionVerificationControllerProxyCallback {
|
enum SessionVerificationControllerProxyAction {
|
||||||
|
case receivedVerificationRequest(details: SessionVerificationRequestDetails)
|
||||||
case acceptedVerificationRequest
|
case acceptedVerificationRequest
|
||||||
case startedSasVerification
|
case startedSasVerification
|
||||||
case receivedVerificationData([SessionVerificationEmoji])
|
case receivedVerificationData([SessionVerificationEmoji])
|
||||||
@ -25,6 +29,14 @@ enum SessionVerificationControllerProxyCallback {
|
|||||||
case failed
|
case failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SessionVerificationRequestDetails {
|
||||||
|
let senderID: String
|
||||||
|
let flowID: String
|
||||||
|
let deviceID: String
|
||||||
|
let displayName: String?
|
||||||
|
let firstSeenDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionVerificationEmoji: Hashable {
|
struct SessionVerificationEmoji: Hashable {
|
||||||
let symbol: String
|
let symbol: String
|
||||||
let description: String
|
let description: String
|
||||||
@ -36,7 +48,11 @@ struct SessionVerificationEmoji: Hashable {
|
|||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
protocol SessionVerificationControllerProxyProtocol {
|
protocol SessionVerificationControllerProxyProtocol {
|
||||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> { get }
|
var actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> { get }
|
||||||
|
|
||||||
|
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
|
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
|
@ -519,7 +519,8 @@ class MockScreen: Identifiable {
|
|||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
case .sessionVerification:
|
case .sessionVerification:
|
||||||
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
|
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
|
||||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy)
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy,
|
||||||
|
flow: .initiator)
|
||||||
return SessionVerificationScreenCoordinator(parameters: parameters)
|
return SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
case .userSessionScreen, .userSessionScreenReply:
|
case .userSessionScreen, .userSessionScreenReply:
|
||||||
let appSettings: AppSettings = ServiceLocator.shared.settings
|
let appSettings: AppSettings = ServiceLocator.shared.settings
|
||||||
|
@ -755,6 +755,12 @@ extension PreviewTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_sessionVerificationRequestDetailsView() {
|
||||||
|
for preview in SessionVerificationRequestDetailsView_Previews._allPreviews {
|
||||||
|
assertSnapshots(matching: preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func test_sessionVerification() {
|
func test_sessionVerification() {
|
||||||
for preview in SessionVerification_Previews._allPreviews {
|
for preview in SessionVerification_Previews._allPreviews {
|
||||||
assertSnapshots(matching: preview)
|
assertSnapshots(matching: preview)
|
||||||
|
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-en-GB.Set-up.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-en-GB.Set-up.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-pseudo.Set-up.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-pseudo.Set-up.png
(Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Cancelled.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Cancelled.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Declining-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Declining-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Initial-Responder.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Initial-Responder.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Cancelled.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Cancelled.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Declining-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Declining-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Initial-Responder.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Initial-Responder.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Cancelled.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Cancelled.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Declining-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Declining-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Initial-Responder.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Initial-Responder.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-en-GB.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Accepting-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Accepting-Verification-Request.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Cancelled.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Cancelled.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Declining-Challenge.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Declining-Challenge.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Initial-Responder.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Initial-Responder.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.SAS-Verification-started.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-16-pseudo.Starting-SAS-Verification.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPad-en-GB.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPad-en-GB.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPad-pseudo.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPad-pseudo.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_sessionVerificationRequestDetailsView-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/createRoom-1-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/createRoom-1-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/createRoom-1-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/createRoom-1-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/createRoom-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/createRoom-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/createRoom-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/createRoom-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/createRoomNoUsers-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/createRoomNoUsers-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/createRoomNoUsers-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/createRoomNoUsers-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-0-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-0-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-1-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-1-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-1-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-1-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-2-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-2-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-2-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-2-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-3-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-3-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-3-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-3-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-4-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-4-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-4-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-4-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-5-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-5-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-5-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-5-iPhone-16-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-6-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-6-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-7-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/sessionVerification-7-iPad-10th-generation-en-GB.UI.png
(Stored with Git LFS)
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user