mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +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 */; };
|
||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.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 */; };
|
||||
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -4650,6 +4652,7 @@
|
||||
A722D372674EE5687E1A67E4 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */,
|
||||
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
@ -6904,6 +6907,7 @@
|
||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
||||
91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */,
|
||||
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */,
|
||||
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */,
|
||||
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */,
|
||||
@ -7828,7 +7832,7 @@
|
||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 1.0.61;
|
||||
version = 1.0.62;
|
||||
};
|
||||
};
|
||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||
|
@ -149,8 +149,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||
"state" : {
|
||||
"revision" : "2e6378514e79a648d436e8faeb8cd8106910cf0b",
|
||||
"version" : "1.0.61"
|
||||
"revision" : "9b26e40ae6c27c56e233577c863569ff074f84fd",
|
||||
"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_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.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.title" = "Set up recovery";
|
||||
"banner_set_up_recovery_submit" = "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_acceptable_use_policy" = "Acceptable use policy";
|
||||
"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_subtitle" = "What is the address of 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_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";
|
||||
@ -538,10 +539,10 @@
|
||||
"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_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_point_1" = "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_title" = "Are you sure you want to turn off backup?";
|
||||
"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" = "You will not have encrypted message history on new devices";
|
||||
"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 key storage and delete it?";
|
||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||
"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’";
|
||||
@ -652,7 +653,7 @@
|
||||
"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_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_description" = "Do not share this with anyone!";
|
||||
"screen_recovery_key_setup_success" = "Recovery setup successful";
|
||||
|
@ -244,9 +244,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
switch action {
|
||||
case .otherDevice:
|
||||
Task {
|
||||
await self.presentSessionVerificationScreen()
|
||||
}
|
||||
presentSessionVerificationScreen()
|
||||
case .recoveryKey:
|
||||
presentRecoveryKeyScreen()
|
||||
case .skip:
|
||||
@ -263,12 +261,13 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentSessionVerificationScreen() async {
|
||||
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
|
||||
private func presentSessionVerificationScreen() {
|
||||
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
||||
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)
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
import AVKit
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
enum UserSessionFlowCoordinatorAction {
|
||||
@ -59,7 +60,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
/// For testing purposes.
|
||||
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
init(userSession: UserSessionProtocol,
|
||||
navigationRootCoordinator: NavigationRootCoordinator,
|
||||
appLockService: AppLockServiceProtocol,
|
||||
@ -113,87 +113,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
userSession.sessionSecurityStatePublisher
|
||||
.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)
|
||||
setupObservers()
|
||||
}
|
||||
|
||||
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() {
|
||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
||||
bugReportService: bugReportService,
|
||||
@ -569,7 +612,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self?.stateMachine.processEvent(.dismissedStartChatScreen)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Session Verification
|
||||
|
||||
// MARK: Calls
|
||||
|
||||
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") }
|
||||
/// Upgrade available
|
||||
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
|
||||
internal static var commonAbout: String { return L10n.tr("Localizable", "common_about") }
|
||||
/// Acceptable use policy
|
||||
@ -1009,7 +1011,7 @@ internal enum L10n {
|
||||
internal static var screenChangeServerSubtitle: String { return L10n.tr("Localizable", "screen_change_server_subtitle") }
|
||||
/// Select your server
|
||||
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") }
|
||||
/// Turn on backup
|
||||
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") }
|
||||
/// Are you sure you want to turn off backup?
|
||||
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") }
|
||||
/// 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") }
|
||||
/// 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 {
|
||||
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") }
|
||||
/// This account has been deactivated.
|
||||
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") }
|
||||
/// Have you saved your recovery key?
|
||||
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") }
|
||||
/// Generate your recovery 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 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") }
|
||||
/// Set up recovery
|
||||
/// Set up recovery to protect your account
|
||||
internal static var title: String { return L10n.tr("Localizable", "banner.set_up_recovery.title") }
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,6 @@ extension ClientProxyMock {
|
||||
logoutReturnValue = nil
|
||||
searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false))
|
||||
profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user"))
|
||||
sessionVerificationControllerProxyReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
|
||||
ignoreUserReturnValue = .success(())
|
||||
unignoreUserReturnValue = .success(())
|
||||
|
||||
|
@ -2210,6 +2210,7 @@ class ClientProxyMock: ClientProxyProtocol {
|
||||
set(value) { underlyingSecureBackupController = value }
|
||||
}
|
||||
var underlyingSecureBackupController: SecureBackupControllerProtocol!
|
||||
var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||
|
||||
//MARK: - isOnlyDeviceLeft
|
||||
|
||||
@ -3519,70 +3520,6 @@ class ClientProxyMock: ClientProxyProtocol {
|
||||
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
|
||||
|
||||
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
|
||||
@ -13435,12 +13372,146 @@ class SecureBackupControllerMock: SecureBackupControllerProtocol {
|
||||
}
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
get { return underlyingCallbacks }
|
||||
set(value) { underlyingCallbacks = value }
|
||||
var actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> {
|
||||
get { return underlyingActions }
|
||||
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
|
||||
|
||||
var requestVerificationUnderlyingCallsCount = 0
|
||||
|
@ -16529,6 +16529,92 @@ open class SessionVerificationControllerSDKMock: MatrixRustSDK.SessionVerificati
|
||||
|
||||
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
|
||||
|
||||
open var approveVerificationThrowableError: Error?
|
||||
@ -16649,75 +16735,6 @@ open class SessionVerificationControllerSDKMock: MatrixRustSDK.SessionVerificati
|
||||
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
|
||||
|
||||
open var requestVerificationThrowableError: Error?
|
||||
|
@ -16,16 +16,18 @@ extension SessionVerificationControllerProxyMock {
|
||||
SessionVerificationEmoji(symbol: "🏁", description: "Flag"),
|
||||
SessionVerificationEmoji(symbol: "🌏", description: "Globe")]
|
||||
|
||||
static func configureMock(callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> = .init(),
|
||||
static func configureMock(actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> = .init(),
|
||||
isVerified: Bool = false,
|
||||
requestDelay: Duration = .seconds(1)) -> SessionVerificationControllerProxyMock {
|
||||
let mock = SessionVerificationControllerProxyMock()
|
||||
mock.underlyingCallbacks = callbacks
|
||||
mock.underlyingActions = actions
|
||||
|
||||
mock.acknowledgeVerificationRequestDetailsReturnValue = .success(())
|
||||
|
||||
mock.requestVerificationClosure = { [unowned mock] in
|
||||
Task.detached {
|
||||
try await Task.sleep(for: requestDelay)
|
||||
mock.callbacks.send(.acceptedVerificationRequest)
|
||||
mock.actions.send(.acceptedVerificationRequest)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
@ -34,11 +36,11 @@ extension SessionVerificationControllerProxyMock {
|
||||
mock.startSasVerificationClosure = { [unowned mock] in
|
||||
Task.detached {
|
||||
try await Task.sleep(for: requestDelay)
|
||||
mock.callbacks.send(.startedSasVerification)
|
||||
mock.actions.send(.startedSasVerification)
|
||||
|
||||
Task.detached {
|
||||
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
|
||||
Task.detached {
|
||||
try await Task.sleep(for: requestDelay)
|
||||
mock.callbacks.send(.finished)
|
||||
mock.actions.send(.finished)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
@ -57,7 +59,7 @@ extension SessionVerificationControllerProxyMock {
|
||||
mock.declineVerificationClosure = { [unowned mock] in
|
||||
Task.detached {
|
||||
try await Task.sleep(for: requestDelay)
|
||||
mock.callbacks.send(.cancelled)
|
||||
mock.actions.send(.cancelled)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
@ -66,7 +68,7 @@ extension SessionVerificationControllerProxyMock {
|
||||
mock.cancelVerificationClosure = { [unowned mock] in
|
||||
Task.detached {
|
||||
try await Task.sleep(for: requestDelay)
|
||||
mock.callbacks.send(.cancelled)
|
||||
mock.actions.send(.cancelled)
|
||||
}
|
||||
|
||||
return .success(())
|
||||
|
@ -184,6 +184,8 @@ enum A11yIdentifiers {
|
||||
}
|
||||
|
||||
struct SessionVerificationScreen {
|
||||
let acceptVerificationRequest = "session_verification-accept_verification_request"
|
||||
let ignoreVerificationRequest = "session_verification-ignore_verification_request"
|
||||
let requestVerification = "session_verification-request_verification"
|
||||
let startSasVerification = "session_verification-start_sas_verification"
|
||||
let acceptChallenge = "session_verification-accept_challenge"
|
||||
|
@ -6,14 +6,21 @@
|
||||
//
|
||||
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
enum SessionVerificationScreenCoordinatorAction {
|
||||
case done
|
||||
}
|
||||
|
||||
enum SessionVerificationScreenFlow {
|
||||
case initiator
|
||||
case responder(details: SessionVerificationRequestDetails)
|
||||
}
|
||||
|
||||
struct SessionVerificationScreenCoordinatorParameters {
|
||||
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||
let flow: SessionVerificationScreenFlow
|
||||
}
|
||||
|
||||
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||
@ -27,7 +34,8 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
init(parameters: SessionVerificationScreenCoordinatorParameters) {
|
||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy)
|
||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy,
|
||||
flow: parameters.flow)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
@ -11,13 +11,61 @@ enum SessionVerificationScreenViewModelAction {
|
||||
case finished
|
||||
}
|
||||
|
||||
enum SessionVerificationScreenViewAction {
|
||||
case acceptVerificationRequest
|
||||
case ignoreVerificationRequest
|
||||
case requestVerification
|
||||
case startSasVerification
|
||||
case restart
|
||||
case accept
|
||||
case decline
|
||||
case done
|
||||
}
|
||||
|
||||
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? {
|
||||
switch verificationState {
|
||||
case .initial:
|
||||
return L10n.screenSessionVerificationOpenExistingSessionTitle
|
||||
switch flow {
|
||||
case .initiator:
|
||||
return L10n.screenSessionVerificationOpenExistingSessionTitle
|
||||
case .responder:
|
||||
return L10n.screenSessionVerificationRequestTitle
|
||||
}
|
||||
case .acceptingVerificationRequest:
|
||||
return L10n.screenSessionVerificationRequestTitle
|
||||
case .requestingVerification:
|
||||
return L10n.screenSessionVerificationWaitingToAcceptTitle
|
||||
case .verificationRequestAccepted:
|
||||
@ -31,13 +79,13 @@ struct SessionVerificationScreenViewState: BindableState {
|
||||
case .acceptingChallenge:
|
||||
return L10n.screenSessionVerificationCompareEmojisTitle
|
||||
case .decliningChallenge:
|
||||
return nil
|
||||
return L10n.screenSessionVerificationCompareEmojisTitle
|
||||
case .verified:
|
||||
return L10n.commonVerificationComplete
|
||||
case .cancelling:
|
||||
return nil
|
||||
case .cancelled:
|
||||
return L10n.commonVerificationCancelled
|
||||
return L10n.commonVerificationFailed
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +96,14 @@ struct SessionVerificationScreenViewState: BindableState {
|
||||
var message: String {
|
||||
switch verificationState {
|
||||
case .initial:
|
||||
return L10n.screenSessionVerificationOpenExistingSessionSubtitle
|
||||
switch flow {
|
||||
case .initiator:
|
||||
return L10n.screenSessionVerificationOpenExistingSessionSubtitle
|
||||
case .responder:
|
||||
return L10n.screenSessionVerificationRequestSubtitle
|
||||
}
|
||||
case .acceptingVerificationRequest:
|
||||
return L10n.screenSessionVerificationRequestSubtitle
|
||||
case .requestingVerification:
|
||||
return L10n.screenSessionVerificationWaitingToAcceptSubtitle
|
||||
case .verificationRequestAccepted:
|
||||
@ -60,7 +115,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
||||
case .acceptingChallenge:
|
||||
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||
case .decliningChallenge:
|
||||
return L10n.commonWaiting
|
||||
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||
case .cancelling:
|
||||
return L10n.commonWaiting
|
||||
case .showingChallenge:
|
||||
@ -68,15 +123,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
||||
case .verified:
|
||||
return L10n.screenSessionVerificationCompleteSubtitle
|
||||
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 {
|
||||
/// The initial state, before verification started
|
||||
case initial
|
||||
/// Accepting the remote verification request
|
||||
case acceptingVerificationRequest
|
||||
/// Waiting for verification acceptance
|
||||
case requestingVerification
|
||||
/// Verification request accepted. Waiting for start
|
||||
@ -37,6 +39,8 @@ class SessionVerificationScreenStateMachine {
|
||||
|
||||
/// Events that can be triggered on the SessionVerification state machine
|
||||
enum Event: EventType {
|
||||
/// Accept the remote verification request
|
||||
case acceptVerificationRequest
|
||||
/// Request verification
|
||||
case requestVerification
|
||||
/// The current verification request has been accepted
|
||||
@ -69,16 +73,23 @@ class SessionVerificationScreenStateMachine {
|
||||
stateMachine.state
|
||||
}
|
||||
|
||||
init() {
|
||||
stateMachine = StateMachine(state: .initial)
|
||||
init(state: State) {
|
||||
stateMachine = StateMachine(state: state)
|
||||
configure()
|
||||
}
|
||||
|
||||
private func configure() {
|
||||
stateMachine.addRoutes(event: .acceptVerificationRequest, transitions: [.initial => .acceptingVerificationRequest])
|
||||
stateMachine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
||||
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.requestingVerification => .verificationRequestAccepted])
|
||||
|
||||
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.acceptingVerificationRequest => .verificationRequestAccepted,
|
||||
.requestingVerification => .verificationRequestAccepted])
|
||||
|
||||
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])
|
||||
|
||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||
|
@ -12,6 +12,7 @@ typealias SessionVerificationViewModelType = StateStoreViewModel<SessionVerifica
|
||||
|
||||
class SessionVerificationScreenViewModel: SessionVerificationViewModelType, SessionVerificationScreenViewModelProtocol {
|
||||
private let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||
private let flow: SessionVerificationScreenFlow
|
||||
|
||||
private var stateMachine: SessionVerificationScreenStateMachine
|
||||
|
||||
@ -22,21 +23,25 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
}
|
||||
|
||||
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
||||
flow: SessionVerificationScreenFlow,
|
||||
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
|
||||
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()
|
||||
|
||||
sessionVerificationControllerProxy.callbacks
|
||||
sessionVerificationControllerProxy.actions
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
guard let self else { return }
|
||||
|
||||
switch callback {
|
||||
case .receivedVerificationRequest:
|
||||
break // Incoming verification requests are handled on the higher levels
|
||||
case .acceptedVerificationRequest:
|
||||
self.stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||
case .startedSasVerification:
|
||||
@ -57,10 +62,20 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
if case .responder(let details) = flow {
|
||||
Task {
|
||||
await self.sessionVerificationControllerProxy.acknowledgeVerificationRequest(details: details)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func process(viewAction: SessionVerificationScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .acceptVerificationRequest:
|
||||
stateMachine.processEvent(.acceptVerificationRequest)
|
||||
case .ignoreVerificationRequest:
|
||||
actionsSubject.send(.finished)
|
||||
case .requestVerification:
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
case .startSasVerification:
|
||||
@ -71,13 +86,16 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
stateMachine.processEvent(.acceptChallenge)
|
||||
case .decline:
|
||||
stateMachine.processEvent(.declineChallenge)
|
||||
case .done:
|
||||
actionsSubject.send(.finished)
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
let uncancellableStates: [SessionVerificationScreenStateMachine.State] = [.initial, .verified, .cancelled]
|
||||
|
||||
if !uncancellableStates.contains(stateMachine.state) {
|
||||
switch stateMachine.state {
|
||||
case .initial, .verified, .cancelled: // non-cancellable states
|
||||
return
|
||||
default:
|
||||
stateMachine.processEvent(.cancel)
|
||||
}
|
||||
}
|
||||
@ -91,6 +109,8 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
state.verificationState = context.toState
|
||||
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (.initial, .acceptVerificationRequest, .acceptingVerificationRequest):
|
||||
acceptVerificationRequest()
|
||||
case (.initial, .requestVerification, .requestingVerification):
|
||||
requestVerification()
|
||||
case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification):
|
||||
@ -103,6 +123,10 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
cancelVerification()
|
||||
case (_, _, .verified):
|
||||
actionsSubject.send(.finished)
|
||||
case (.initial, _, .cancelled):
|
||||
if case .responder = flow {
|
||||
actionsSubject.send(.finished)
|
||||
}
|
||||
default:
|
||||
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() {
|
||||
Task {
|
||||
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.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
struct SessionVerificationScreen: View {
|
||||
@ -14,7 +16,6 @@ struct SessionVerificationScreen: View {
|
||||
FullscreenDialog {
|
||||
VStack(spacing: 32) {
|
||||
screenHeader
|
||||
Spacer()
|
||||
mainContent
|
||||
}
|
||||
} bottomContent: {
|
||||
@ -27,33 +28,6 @@ struct SessionVerificationScreen: View {
|
||||
|
||||
// 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
|
||||
private var screenHeader: some View {
|
||||
VStack(spacing: 0) {
|
||||
@ -61,11 +35,11 @@ struct SessionVerificationScreen: View {
|
||||
BigIcon(icon: \.lockSolid)
|
||||
.padding(.bottom, 16)
|
||||
} else {
|
||||
Image(systemName: headerImageName)
|
||||
Image(systemName: context.viewState.headerImageName)
|
||||
.bigIcon()
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
|
||||
|
||||
Text(context.viewState.title ?? "")
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
@ -83,18 +57,17 @@ struct SessionVerificationScreen: View {
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
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)
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
||||
case .acceptingChallenge(let emojis):
|
||||
emojisPanel(with: emojis)
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
||||
case .requestingVerification:
|
||||
ProgressView()
|
||||
.tint(.compound.textSecondary)
|
||||
.scaleEffect(2)
|
||||
default:
|
||||
// In All other cases, we just want an empty view
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
@ -119,18 +92,41 @@ struct SessionVerificationScreen: View {
|
||||
private var actionButtons: some View {
|
||||
switch context.viewState.verificationState {
|
||||
case .initial:
|
||||
VStack(spacing: 32) {
|
||||
switch context.viewState.flow {
|
||||
case .initiator:
|
||||
Button(L10n.actionStartVerification) {
|
||||
context.send(viewAction: .requestVerification)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.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:
|
||||
Button(L10n.actionRetry) {
|
||||
context.send(viewAction: .restart)
|
||||
switch context.viewState.flow {
|
||||
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:
|
||||
Button(L10n.actionStart) {
|
||||
@ -150,31 +146,15 @@ struct SessionVerificationScreen: View {
|
||||
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
||||
context.send(viewAction: .decline)
|
||||
}
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.buttonStyle(.compound(.plain))
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
||||
}
|
||||
|
||||
case .acceptingChallenge:
|
||||
VStack(spacing: 32) {
|
||||
Button { context.send(viewAction: .accept) } label: {
|
||||
HStack(spacing: 16) {
|
||||
ProgressView()
|
||||
.tint(.compound.textOnSolidPrimary)
|
||||
Text(L10n.screenSessionVerificationTheyMatch)
|
||||
}
|
||||
}
|
||||
case .acceptingVerificationRequest, .acceptingChallenge, .decliningChallenge, .requestingVerification:
|
||||
Button(L10n.screenIdentityWaitingOnOtherDevice) { }
|
||||
.buttonStyle(.compound(.primary))
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
|
||||
.disabled(true)
|
||||
|
||||
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
||||
context.send(viewAction: .decline)
|
||||
}
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
||||
.disabled(true)
|
||||
}
|
||||
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
@ -196,27 +176,50 @@ struct SessionVerificationScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
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)
|
||||
.previewDisplayName("Requesting Verification")
|
||||
sessionVerificationScreen(state: .verificationRequestAccepted)
|
||||
.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))
|
||||
.previewDisplayName("Showing Challenge")
|
||||
sessionVerificationScreen(state: .acceptingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||
.previewDisplayName("Accepting Challenge")
|
||||
sessionVerificationScreen(state: .decliningChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||
.previewDisplayName("Declining Challenge")
|
||||
|
||||
sessionVerificationScreen(state: .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(),
|
||||
flow: flow,
|
||||
verificationState: state)
|
||||
|
||||
return SessionVerificationScreen(context: viewModel.context)
|
||||
|
@ -91,6 +91,7 @@ class TimelineInteractionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||
|
@ -50,6 +50,8 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
let secureBackupController: SecureBackupControllerProtocol
|
||||
|
||||
private(set) var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||
|
||||
private static var roomCreationPowerLevelOverrides: PowerLevels {
|
||||
.init(usersDefault: nil,
|
||||
eventsDefault: nil,
|
||||
@ -157,7 +159,16 @@ class ClientProxy: ClientProxyProtocol {
|
||||
updateVerificationState(client.encryption().verificationState())
|
||||
|
||||
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
|
||||
@ -484,7 +495,8 @@ class ClientProxy: ClientProxyProtocol {
|
||||
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
|
||||
do {
|
||||
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 {
|
||||
return .failure(.roomPreviewIsPrivate)
|
||||
} 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? {
|
||||
do {
|
||||
return try await client.logout().flatMap(URL.init(string:))
|
||||
@ -728,6 +730,19 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
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() {
|
||||
loadCachedAvatarURLTask = Task {
|
||||
@ -1079,17 +1094,17 @@ private class SendQueueRoomErrorListenerProxy: SendQueueRoomErrorListener {
|
||||
}
|
||||
|
||||
private extension RoomPreviewDetails {
|
||||
init(_ roomPreview: RoomPreview) {
|
||||
self = RoomPreviewDetails(roomID: roomPreview.roomId,
|
||||
name: roomPreview.name,
|
||||
canonicalAlias: roomPreview.canonicalAlias,
|
||||
topic: roomPreview.topic,
|
||||
avatarURL: roomPreview.avatarUrl.flatMap(URL.init(string:)),
|
||||
memberCount: UInt(roomPreview.numJoinedMembers),
|
||||
isHistoryWorldReadable: roomPreview.isHistoryWorldReadable,
|
||||
isJoined: roomPreview.isJoined,
|
||||
isInvited: roomPreview.isInvited,
|
||||
isPublic: roomPreview.isPublic,
|
||||
canKnock: roomPreview.canKnock)
|
||||
init(_ roomPreviewInfo: RoomPreviewInfo) {
|
||||
self = RoomPreviewDetails(roomID: roomPreviewInfo.roomId,
|
||||
name: roomPreviewInfo.name,
|
||||
canonicalAlias: roomPreviewInfo.canonicalAlias,
|
||||
topic: roomPreviewInfo.topic,
|
||||
avatarURL: roomPreviewInfo.avatarUrl.flatMap(URL.init(string:)),
|
||||
memberCount: UInt(roomPreviewInfo.numJoinedMembers),
|
||||
isHistoryWorldReadable: roomPreviewInfo.isHistoryWorldReadable,
|
||||
isJoined: roomPreviewInfo.membership == .joined,
|
||||
isInvited: roomPreviewInfo.membership == .invited,
|
||||
isPublic: roomPreviewInfo.joinRule == .public,
|
||||
canKnock: roomPreviewInfo.joinRule == .knock)
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
var secureBackupController: SecureBackupControllerProtocol { get }
|
||||
|
||||
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
|
||||
|
||||
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
||||
|
||||
func startSync()
|
||||
@ -155,8 +157,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
func setUserAvatar(media: MediaInfo) 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>
|
||||
|
||||
|
@ -246,10 +246,8 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
#if targetEnvironment(simulator)
|
||||
// This gets called for no reason on simulators, where CallKit
|
||||
// isn't even supported. Ignore
|
||||
return
|
||||
#endif
|
||||
|
||||
// isn't even supported, ignore it.
|
||||
#else
|
||||
if let ongoingCallID {
|
||||
actionsSubject.send(.endCall(roomID: ongoingCallID.roomID))
|
||||
}
|
||||
@ -257,6 +255,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
||||
tearDownCallSession(sendEndCallAction: false)
|
||||
|
||||
action.fulfill()
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
@ -18,6 +18,10 @@ private class WeakSessionVerificationControllerProxy: SessionVerificationControl
|
||||
|
||||
// MARK: - SessionVerificationControllerDelegate
|
||||
|
||||
func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||
proxy?.didReceiveVerificationRequest(details: details)
|
||||
}
|
||||
|
||||
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
|
||||
switch data {
|
||||
// We can handle only emojis for now
|
||||
@ -54,86 +58,142 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
|
||||
|
||||
init(sessionVerificationController: SessionVerificationController) {
|
||||
self.sessionVerificationController = sessionVerificationController
|
||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||
}
|
||||
|
||||
deinit {
|
||||
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> {
|
||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||
MXLog.info("Requesting session verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.requestVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||
return .failure(.failedRequestingVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Starting SAS verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.startSasVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed starting SAS verification with error: \(error)")
|
||||
return .failure(.failedStartingSasVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Approving verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.approveVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed approving verification with error: \(error)")
|
||||
return .failure(.failedApprovingVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Declining verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.declineVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed declining verification with error: \(error)")
|
||||
return .failure(.failedDecliningVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Cancelling verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.cancelVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed cancelling verification with error: \(error)")
|
||||
return .failure(.failedCancellingVerification)
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
callbacks.send(.acceptedVerificationRequest)
|
||||
MXLog.info("Accepted verification request")
|
||||
|
||||
actions.send(.acceptedVerificationRequest)
|
||||
}
|
||||
|
||||
fileprivate func didStartSasVerification() {
|
||||
callbacks.send(.startedSasVerification)
|
||||
MXLog.info("Started SAS verification")
|
||||
|
||||
actions.send(.startedSasVerification)
|
||||
}
|
||||
|
||||
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())
|
||||
}))
|
||||
}
|
||||
|
||||
fileprivate func didFail() {
|
||||
callbacks.send(.failed)
|
||||
actions.send(.failed)
|
||||
}
|
||||
|
||||
fileprivate func didFinish() {
|
||||
callbacks.send(.finished)
|
||||
actions.send(.finished)
|
||||
}
|
||||
|
||||
fileprivate func didCancel() {
|
||||
callbacks.send(.cancelled)
|
||||
actions.send(.cancelled)
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,11 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum SessionVerificationControllerProxyError: Error {
|
||||
case failedAcknowledgingVerificationRequest
|
||||
case failedAcceptingVerificationRequest
|
||||
case failedRequestingVerification
|
||||
case failedStartingSasVerification
|
||||
case failedApprovingVerification
|
||||
@ -16,7 +19,8 @@ enum SessionVerificationControllerProxyError: Error {
|
||||
case failedCancellingVerification
|
||||
}
|
||||
|
||||
enum SessionVerificationControllerProxyCallback {
|
||||
enum SessionVerificationControllerProxyAction {
|
||||
case receivedVerificationRequest(details: SessionVerificationRequestDetails)
|
||||
case acceptedVerificationRequest
|
||||
case startedSasVerification
|
||||
case receivedVerificationData([SessionVerificationEmoji])
|
||||
@ -25,6 +29,14 @@ enum SessionVerificationControllerProxyCallback {
|
||||
case failed
|
||||
}
|
||||
|
||||
struct SessionVerificationRequestDetails {
|
||||
let senderID: String
|
||||
let flowID: String
|
||||
let deviceID: String
|
||||
let displayName: String?
|
||||
let firstSeenDate: Date
|
||||
}
|
||||
|
||||
struct SessionVerificationEmoji: Hashable {
|
||||
let symbol: String
|
||||
let description: String
|
||||
@ -36,7 +48,11 @@ struct SessionVerificationEmoji: Hashable {
|
||||
|
||||
// sourcery: AutoMockable
|
||||
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>
|
||||
|
||||
|
@ -519,7 +519,8 @@ class MockScreen: Identifiable {
|
||||
return navigationStackCoordinator
|
||||
case .sessionVerification:
|
||||
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy)
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy,
|
||||
flow: .initiator)
|
||||
return SessionVerificationScreenCoordinator(parameters: parameters)
|
||||
case .userSessionScreen, .userSessionScreenReply:
|
||||
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() {
|
||||
for preview in SessionVerification_Previews._allPreviews {
|
||||
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