mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 20:38:41 +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:
@@ -666,6 +666,7 @@
|
|||||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||||
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
||||||
|
91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */; };
|
||||||
92012C96039BC8C2CAEBA9E2 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671C338B7259DC5774816885 /* AuthenticationServiceTests.swift */; };
|
92012C96039BC8C2CAEBA9E2 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671C338B7259DC5774816885 /* AuthenticationServiceTests.swift */; };
|
||||||
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
||||||
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; };
|
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; };
|
||||||
@@ -1314,6 +1315,7 @@
|
|||||||
190EC7285D3CFEF0D3011BCF /* GeoURI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURI.swift; sourceTree = "<group>"; };
|
190EC7285D3CFEF0D3011BCF /* GeoURI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURI.swift; sourceTree = "<group>"; };
|
||||||
196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTrackerViewModifier.swift; sourceTree = "<group>"; };
|
196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTrackerViewModifier.swift; sourceTree = "<group>"; };
|
||||||
1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderProtocol.swift; sourceTree = "<group>"; };
|
1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationRequestDetailsView.swift; sourceTree = "<group>"; };
|
||||||
1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = "<group>"; };
|
1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = "<group>"; };
|
||||||
1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMap.swift; sourceTree = "<group>"; };
|
1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMap.swift; sourceTree = "<group>"; };
|
||||||
1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; };
|
1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; };
|
||||||
@@ -4650,6 +4652,7 @@
|
|||||||
A722D372674EE5687E1A67E4 /* View */ = {
|
A722D372674EE5687E1A67E4 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */,
|
||||||
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */,
|
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
@@ -6904,6 +6907,7 @@
|
|||||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||||
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
||||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
||||||
|
91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */,
|
||||||
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */,
|
707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */,
|
||||||
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */,
|
D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */,
|
||||||
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */,
|
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */,
|
||||||
@@ -7828,7 +7832,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.0.61;
|
version = 1.0.62;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
|||||||
@@ -149,8 +149,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "2e6378514e79a648d436e8faeb8cd8106910cf0b",
|
"revision" : "9b26e40ae6c27c56e233577c863569ff074f84fd",
|
||||||
"version" : "1.0.61"
|
"version" : "1.0.62"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -115,8 +115,9 @@
|
|||||||
"banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later.";
|
"banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later.";
|
||||||
"banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app.";
|
"banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app.";
|
||||||
"banner_migrate_to_native_sliding_sync_title" = "Upgrade available";
|
"banner_migrate_to_native_sliding_sync_title" = "Upgrade available";
|
||||||
"banner.set_up_recovery.content" = "Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices.";
|
"banner_set_up_recovery_submit" = "Set up recovery";
|
||||||
"banner.set_up_recovery.title" = "Set up recovery";
|
"banner.set_up_recovery.content" = "Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices.";
|
||||||
|
"banner.set_up_recovery.title" = "Set up recovery to protect your account";
|
||||||
"common_about" = "About";
|
"common_about" = "About";
|
||||||
"common_acceptable_use_policy" = "Acceptable use policy";
|
"common_acceptable_use_policy" = "Acceptable use policy";
|
||||||
"common_advanced_settings" = "Advanced settings";
|
"common_advanced_settings" = "Advanced settings";
|
||||||
@@ -454,7 +455,7 @@
|
|||||||
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
|
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
|
||||||
"screen_change_server_subtitle" = "What is the address of your server?";
|
"screen_change_server_subtitle" = "What is the address of your server?";
|
||||||
"screen_change_server_title" = "Select your server";
|
"screen_change_server_title" = "Select your server";
|
||||||
"screen_chat_backup_key_backup_action_disable" = "Turn off backup";
|
"screen_chat_backup_key_backup_action_disable" = "Delete key storage";
|
||||||
"screen_chat_backup_key_backup_action_enable" = "Turn on backup";
|
"screen_chat_backup_key_backup_action_enable" = "Turn on backup";
|
||||||
"screen_chat_backup_key_backup_description" = "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices. %1$@.";
|
"screen_chat_backup_key_backup_description" = "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices. %1$@.";
|
||||||
"screen_chat_backup_key_backup_title" = "Key storage";
|
"screen_chat_backup_key_backup_title" = "Key storage";
|
||||||
@@ -538,10 +539,10 @@
|
|||||||
"screen_key_backup_disable_confirmation_action_turn_off" = "Turn off";
|
"screen_key_backup_disable_confirmation_action_turn_off" = "Turn off";
|
||||||
"screen_key_backup_disable_confirmation_description" = "You will lose your encrypted messages if you are signed out of all devices.";
|
"screen_key_backup_disable_confirmation_description" = "You will lose your encrypted messages if you are signed out of all devices.";
|
||||||
"screen_key_backup_disable_confirmation_title" = "Are you sure you want to turn off backup?";
|
"screen_key_backup_disable_confirmation_title" = "Are you sure you want to turn off backup?";
|
||||||
"screen_key_backup_disable_description" = "Turning off backup will remove your current encryption key backup and turn off other security features. In this case, you will:";
|
"screen_key_backup_disable_description" = "Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:";
|
||||||
"screen_key_backup_disable_description_point_1" = "Not have encrypted message history on new devices";
|
"screen_key_backup_disable_description_point_1" = "You will not have encrypted message history on new devices";
|
||||||
"screen_key_backup_disable_description_point_2" = "Lose access to your encrypted messages if you are signed out of %1$@ everywhere";
|
"screen_key_backup_disable_description_point_2" = "You will lose access to your encrypted messages if you are signed out of %1$@ everywhere";
|
||||||
"screen_key_backup_disable_title" = "Are you sure you want to turn off backup?";
|
"screen_key_backup_disable_title" = "Are you sure you want to turn off key storage and delete it?";
|
||||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||||
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
||||||
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
||||||
@@ -652,7 +653,7 @@
|
|||||||
"screen_recovery_key_save_title" = "Save your recovery key somewhere safe";
|
"screen_recovery_key_save_title" = "Save your recovery key somewhere safe";
|
||||||
"screen_recovery_key_setup_confirmation_description" = "You will not be able to access your new recovery key after this step.";
|
"screen_recovery_key_setup_confirmation_description" = "You will not be able to access your new recovery key after this step.";
|
||||||
"screen_recovery_key_setup_confirmation_title" = "Have you saved your recovery key?";
|
"screen_recovery_key_setup_confirmation_title" = "Have you saved your recovery key?";
|
||||||
"screen_recovery_key_setup_description" = "Your chat backup is protected by a recovery key. If you need a new recovery key after setup you can recreate by selecting ‘Change recovery key’.";
|
"screen_recovery_key_setup_description" = "Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting ‘Change recovery key’.";
|
||||||
"screen_recovery_key_setup_generate_key" = "Generate your recovery key";
|
"screen_recovery_key_setup_generate_key" = "Generate your recovery key";
|
||||||
"screen_recovery_key_setup_generate_key_description" = "Do not share this with anyone!";
|
"screen_recovery_key_setup_generate_key_description" = "Do not share this with anyone!";
|
||||||
"screen_recovery_key_setup_success" = "Recovery setup successful";
|
"screen_recovery_key_setup_success" = "Recovery setup successful";
|
||||||
|
|||||||
@@ -244,9 +244,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .otherDevice:
|
case .otherDevice:
|
||||||
Task {
|
presentSessionVerificationScreen()
|
||||||
await self.presentSessionVerificationScreen()
|
|
||||||
}
|
|
||||||
case .recoveryKey:
|
case .recoveryKey:
|
||||||
presentRecoveryKeyScreen()
|
presentRecoveryKeyScreen()
|
||||||
case .skip:
|
case .skip:
|
||||||
@@ -263,12 +261,13 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentCoordinator(coordinator)
|
presentCoordinator(coordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSessionVerificationScreen() async {
|
private func presentSessionVerificationScreen() {
|
||||||
guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else {
|
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
||||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||||
|
flow: .initiator)
|
||||||
|
|
||||||
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
|
import MatrixRustSDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum UserSessionFlowCoordinatorAction {
|
enum UserSessionFlowCoordinatorAction {
|
||||||
@@ -59,7 +60,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
/// For testing purposes.
|
/// For testing purposes.
|
||||||
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(userSession: UserSessionProtocol,
|
||||||
navigationRootCoordinator: NavigationRootCoordinator,
|
navigationRootCoordinator: NavigationRootCoordinator,
|
||||||
appLockService: AppLockServiceProtocol,
|
appLockService: AppLockServiceProtocol,
|
||||||
@@ -113,87 +113,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
|
|
||||||
userSession.sessionSecurityStatePublisher
|
setupObservers()
|
||||||
.map(\.verificationState)
|
|
||||||
.filter { $0 != .unknown }
|
|
||||||
.removeDuplicates()
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] _ in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
attemptStartingOnboarding()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
settingsFlowCoordinator.actions.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .presentedSettings:
|
|
||||||
stateMachine.processEvent(.showSettingsScreen)
|
|
||||||
case .dismissedSettings:
|
|
||||||
stateMachine.processEvent(.dismissedSettingsScreen)
|
|
||||||
case .runLogoutFlow:
|
|
||||||
Task { await self.runLogoutFlow() }
|
|
||||||
case .clearCache:
|
|
||||||
actionsSubject.send(.clearCache)
|
|
||||||
case .forceLogout:
|
|
||||||
actionsSubject.send(.forceLogout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
userSession.clientProxy.actionsPublisher
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { action in
|
|
||||||
guard case let .receivedDecryptionError(info) = action else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeToDecryptMs: Int
|
|
||||||
if let unsignedTimeToDecryptMs = info.timeToDecryptMs {
|
|
||||||
timeToDecryptMs = Int(unsignedTimeToDecryptMs)
|
|
||||||
} else {
|
|
||||||
timeToDecryptMs = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
switch info.cause {
|
|
||||||
case .unknown:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .UnknownError, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .unknownDevice:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .unsignedDevice:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .verificationViolation:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedVerificationViolation, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
case .sentBeforeWeJoined:
|
|
||||||
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedDueToMembership, timeToDecryptMillis: timeToDecryptMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
elementCallService.actions
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] action in
|
|
||||||
switch action {
|
|
||||||
case .endCall:
|
|
||||||
self?.dismissCallScreenIfNeeded()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
onboardingFlowCoordinator.actions
|
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .logout:
|
|
||||||
logout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@@ -385,6 +305,129 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupObservers() {
|
||||||
|
userSession.sessionSecurityStatePublisher
|
||||||
|
.map(\.verificationState)
|
||||||
|
.filter { $0 != .unknown }
|
||||||
|
.removeDuplicates()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
attemptStartingOnboarding()
|
||||||
|
|
||||||
|
setupSessionVerificationRequestsObserver()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
settingsFlowCoordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .presentedSettings:
|
||||||
|
stateMachine.processEvent(.showSettingsScreen)
|
||||||
|
case .dismissedSettings:
|
||||||
|
stateMachine.processEvent(.dismissedSettingsScreen)
|
||||||
|
case .runLogoutFlow:
|
||||||
|
Task { await self.runLogoutFlow() }
|
||||||
|
case .clearCache:
|
||||||
|
actionsSubject.send(.clearCache)
|
||||||
|
case .forceLogout:
|
||||||
|
actionsSubject.send(.forceLogout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
userSession.clientProxy.actionsPublisher
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self, case let .receivedDecryptionError(info) = action else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeToDecryptMs: Int
|
||||||
|
if let unsignedTimeToDecryptMs = info.timeToDecryptMs {
|
||||||
|
timeToDecryptMs = Int(unsignedTimeToDecryptMs)
|
||||||
|
} else {
|
||||||
|
timeToDecryptMs = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
switch info.cause {
|
||||||
|
case .unknown:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .UnknownError, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .unknownDevice:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .unsignedDevice:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedSentByInsecureDevice, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .verificationViolation:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedVerificationViolation, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
case .sentBeforeWeJoined:
|
||||||
|
analytics.trackError(context: nil, domain: .E2EE, name: .ExpectedDueToMembership, timeToDecryptMillis: timeToDecryptMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
elementCallService.actions
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] action in
|
||||||
|
switch action {
|
||||||
|
case .endCall:
|
||||||
|
self?.dismissCallScreenIfNeeded()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
onboardingFlowCoordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .logout:
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupSessionVerificationRequestsObserver() {
|
||||||
|
userSession.clientProxy.sessionVerificationController?.actions
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self, case .receivedVerificationRequest(let details) = action else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MXLog.info("Received session verification request")
|
||||||
|
|
||||||
|
presentSessionVerificationScreen(details: details)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentSessionVerificationScreen(details: SessionVerificationRequestDetails) {
|
||||||
|
guard let sessionVerificationController = userSession.clientProxy.sessionVerificationController else {
|
||||||
|
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||||
|
}
|
||||||
|
|
||||||
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||||
|
flow: .responder(details: details))
|
||||||
|
|
||||||
|
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
coordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
switch action {
|
||||||
|
case .done:
|
||||||
|
self?.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
navigationSplitCoordinator.setSheetCoordinator(coordinator)
|
||||||
|
}
|
||||||
|
|
||||||
private func presentHomeScreen() {
|
private func presentHomeScreen() {
|
||||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
||||||
bugReportService: bugReportService,
|
bugReportService: bugReportService,
|
||||||
@@ -570,6 +613,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Session Verification
|
||||||
|
|
||||||
// MARK: Calls
|
// MARK: Calls
|
||||||
|
|
||||||
private func presentCallScreen(genericCallLink url: URL) {
|
private func presentCallScreen(genericCallLink url: URL) {
|
||||||
|
|||||||
@@ -266,6 +266,8 @@ internal enum L10n {
|
|||||||
internal static var bannerMigrateToNativeSlidingSyncForceLogoutTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_force_logout_title") }
|
internal static var bannerMigrateToNativeSlidingSyncForceLogoutTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_force_logout_title") }
|
||||||
/// Upgrade available
|
/// Upgrade available
|
||||||
internal static var bannerMigrateToNativeSlidingSyncTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_title") }
|
internal static var bannerMigrateToNativeSlidingSyncTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_title") }
|
||||||
|
/// Set up recovery
|
||||||
|
internal static var bannerSetUpRecoverySubmit: String { return L10n.tr("Localizable", "banner_set_up_recovery_submit") }
|
||||||
/// About
|
/// About
|
||||||
internal static var commonAbout: String { return L10n.tr("Localizable", "common_about") }
|
internal static var commonAbout: String { return L10n.tr("Localizable", "common_about") }
|
||||||
/// Acceptable use policy
|
/// Acceptable use policy
|
||||||
@@ -1009,7 +1011,7 @@ internal enum L10n {
|
|||||||
internal static var screenChangeServerSubtitle: String { return L10n.tr("Localizable", "screen_change_server_subtitle") }
|
internal static var screenChangeServerSubtitle: String { return L10n.tr("Localizable", "screen_change_server_subtitle") }
|
||||||
/// Select your server
|
/// Select your server
|
||||||
internal static var screenChangeServerTitle: String { return L10n.tr("Localizable", "screen_change_server_title") }
|
internal static var screenChangeServerTitle: String { return L10n.tr("Localizable", "screen_change_server_title") }
|
||||||
/// Turn off backup
|
/// Delete key storage
|
||||||
internal static var screenChatBackupKeyBackupActionDisable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_disable") }
|
internal static var screenChatBackupKeyBackupActionDisable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_disable") }
|
||||||
/// Turn on backup
|
/// Turn on backup
|
||||||
internal static var screenChatBackupKeyBackupActionEnable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_enable") }
|
internal static var screenChatBackupKeyBackupActionEnable: String { return L10n.tr("Localizable", "screen_chat_backup_key_backup_action_enable") }
|
||||||
@@ -1258,15 +1260,15 @@ internal enum L10n {
|
|||||||
internal static var screenKeyBackupDisableConfirmationDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_description") }
|
internal static var screenKeyBackupDisableConfirmationDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_description") }
|
||||||
/// Are you sure you want to turn off backup?
|
/// Are you sure you want to turn off backup?
|
||||||
internal static var screenKeyBackupDisableConfirmationTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_title") }
|
internal static var screenKeyBackupDisableConfirmationTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_confirmation_title") }
|
||||||
/// Turning off backup will remove your current encryption key backup and turn off other security features. In this case, you will:
|
/// Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:
|
||||||
internal static var screenKeyBackupDisableDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_description") }
|
internal static var screenKeyBackupDisableDescription: String { return L10n.tr("Localizable", "screen_key_backup_disable_description") }
|
||||||
/// Not have encrypted message history on new devices
|
/// You will not have encrypted message history on new devices
|
||||||
internal static var screenKeyBackupDisableDescriptionPoint1: String { return L10n.tr("Localizable", "screen_key_backup_disable_description_point_1") }
|
internal static var screenKeyBackupDisableDescriptionPoint1: String { return L10n.tr("Localizable", "screen_key_backup_disable_description_point_1") }
|
||||||
/// Lose access to your encrypted messages if you are signed out of %1$@ everywhere
|
/// You will lose access to your encrypted messages if you are signed out of %1$@ everywhere
|
||||||
internal static func screenKeyBackupDisableDescriptionPoint2(_ p1: Any) -> String {
|
internal static func screenKeyBackupDisableDescriptionPoint2(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "screen_key_backup_disable_description_point_2", String(describing: p1))
|
return L10n.tr("Localizable", "screen_key_backup_disable_description_point_2", String(describing: p1))
|
||||||
}
|
}
|
||||||
/// Are you sure you want to turn off backup?
|
/// Are you sure you want to turn off key storage and delete it?
|
||||||
internal static var screenKeyBackupDisableTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_title") }
|
internal static var screenKeyBackupDisableTitle: String { return L10n.tr("Localizable", "screen_key_backup_disable_title") }
|
||||||
/// This account has been deactivated.
|
/// This account has been deactivated.
|
||||||
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
||||||
@@ -1536,7 +1538,7 @@ internal enum L10n {
|
|||||||
internal static var screenRecoveryKeySetupConfirmationDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_description") }
|
internal static var screenRecoveryKeySetupConfirmationDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_description") }
|
||||||
/// Have you saved your recovery key?
|
/// Have you saved your recovery key?
|
||||||
internal static var screenRecoveryKeySetupConfirmationTitle: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_title") }
|
internal static var screenRecoveryKeySetupConfirmationTitle: String { return L10n.tr("Localizable", "screen_recovery_key_setup_confirmation_title") }
|
||||||
/// Your chat backup is protected by a recovery key. If you need a new recovery key after setup you can recreate by selecting ‘Change recovery key’.
|
/// Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting ‘Change recovery key’.
|
||||||
internal static var screenRecoveryKeySetupDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_description") }
|
internal static var screenRecoveryKeySetupDescription: String { return L10n.tr("Localizable", "screen_recovery_key_setup_description") }
|
||||||
/// Generate your recovery key
|
/// Generate your recovery key
|
||||||
internal static var screenRecoveryKeySetupGenerateKey: String { return L10n.tr("Localizable", "screen_recovery_key_setup_generate_key") }
|
internal static var screenRecoveryKeySetupGenerateKey: String { return L10n.tr("Localizable", "screen_recovery_key_setup_generate_key") }
|
||||||
@@ -2499,9 +2501,9 @@ internal enum L10n {
|
|||||||
|
|
||||||
internal enum Banner {
|
internal enum Banner {
|
||||||
internal enum SetUpRecovery {
|
internal enum SetUpRecovery {
|
||||||
/// Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices.
|
/// Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices.
|
||||||
internal static var content: String { return L10n.tr("Localizable", "banner.set_up_recovery.content") }
|
internal static var content: String { return L10n.tr("Localizable", "banner.set_up_recovery.content") }
|
||||||
/// Set up recovery
|
/// Set up recovery to protect your account
|
||||||
internal static var title: String { return L10n.tr("Localizable", "banner.set_up_recovery.title") }
|
internal static var title: String { return L10n.tr("Localizable", "banner.set_up_recovery.title") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ extension ClientProxyMock {
|
|||||||
logoutReturnValue = nil
|
logoutReturnValue = nil
|
||||||
searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false))
|
searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false))
|
||||||
profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user"))
|
profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user"))
|
||||||
sessionVerificationControllerProxyReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
|
|
||||||
ignoreUserReturnValue = .success(())
|
ignoreUserReturnValue = .success(())
|
||||||
unignoreUserReturnValue = .success(())
|
unignoreUserReturnValue = .success(())
|
||||||
|
|
||||||
|
|||||||
@@ -2210,6 +2210,7 @@ class ClientProxyMock: ClientProxyProtocol {
|
|||||||
set(value) { underlyingSecureBackupController = value }
|
set(value) { underlyingSecureBackupController = value }
|
||||||
}
|
}
|
||||||
var underlyingSecureBackupController: SecureBackupControllerProtocol!
|
var underlyingSecureBackupController: SecureBackupControllerProtocol!
|
||||||
|
var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||||
|
|
||||||
//MARK: - isOnlyDeviceLeft
|
//MARK: - isOnlyDeviceLeft
|
||||||
|
|
||||||
@@ -3519,70 +3520,6 @@ class ClientProxyMock: ClientProxyProtocol {
|
|||||||
return removeUserAvatarReturnValue
|
return removeUserAvatarReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - sessionVerificationControllerProxy
|
|
||||||
|
|
||||||
var sessionVerificationControllerProxyUnderlyingCallsCount = 0
|
|
||||||
var sessionVerificationControllerProxyCallsCount: Int {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return sessionVerificationControllerProxyUnderlyingCallsCount
|
|
||||||
} else {
|
|
||||||
var returnValue: Int? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = sessionVerificationControllerProxyUnderlyingCallsCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
sessionVerificationControllerProxyUnderlyingCallsCount = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
sessionVerificationControllerProxyUnderlyingCallsCount = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var sessionVerificationControllerProxyCalled: Bool {
|
|
||||||
return sessionVerificationControllerProxyCallsCount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessionVerificationControllerProxyUnderlyingReturnValue: Result<SessionVerificationControllerProxyProtocol, ClientProxyError>!
|
|
||||||
var sessionVerificationControllerProxyReturnValue: Result<SessionVerificationControllerProxyProtocol, ClientProxyError>! {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return sessionVerificationControllerProxyUnderlyingReturnValue
|
|
||||||
} else {
|
|
||||||
var returnValue: Result<SessionVerificationControllerProxyProtocol, ClientProxyError>? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = sessionVerificationControllerProxyUnderlyingReturnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
sessionVerificationControllerProxyUnderlyingReturnValue = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
sessionVerificationControllerProxyUnderlyingReturnValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var sessionVerificationControllerProxyClosure: (() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>)?
|
|
||||||
|
|
||||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
|
||||||
sessionVerificationControllerProxyCallsCount += 1
|
|
||||||
if let sessionVerificationControllerProxyClosure = sessionVerificationControllerProxyClosure {
|
|
||||||
return await sessionVerificationControllerProxyClosure()
|
|
||||||
} else {
|
|
||||||
return sessionVerificationControllerProxyReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - deactivateAccount
|
//MARK: - deactivateAccount
|
||||||
|
|
||||||
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
|
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
|
||||||
@@ -13435,12 +13372,146 @@ class SecureBackupControllerMock: SecureBackupControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
var actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> {
|
||||||
get { return underlyingCallbacks }
|
get { return underlyingActions }
|
||||||
set(value) { underlyingCallbacks = value }
|
set(value) { underlyingActions = value }
|
||||||
}
|
}
|
||||||
var underlyingCallbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never>!
|
var underlyingActions: PassthroughSubject<SessionVerificationControllerProxyAction, Never>!
|
||||||
|
|
||||||
|
//MARK: - acknowledgeVerificationRequest
|
||||||
|
|
||||||
|
var acknowledgeVerificationRequestDetailsUnderlyingCallsCount = 0
|
||||||
|
var acknowledgeVerificationRequestDetailsCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acknowledgeVerificationRequestDetailsUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acknowledgeVerificationRequestDetailsUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acknowledgeVerificationRequestDetailsCalled: Bool {
|
||||||
|
return acknowledgeVerificationRequestDetailsCallsCount > 0
|
||||||
|
}
|
||||||
|
var acknowledgeVerificationRequestDetailsReceivedDetails: SessionVerificationRequestDetails?
|
||||||
|
var acknowledgeVerificationRequestDetailsReceivedInvocations: [SessionVerificationRequestDetails] = []
|
||||||
|
|
||||||
|
var acknowledgeVerificationRequestDetailsUnderlyingReturnValue: Result<Void, SessionVerificationControllerProxyError>!
|
||||||
|
var acknowledgeVerificationRequestDetailsReturnValue: Result<Void, SessionVerificationControllerProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acknowledgeVerificationRequestDetailsUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Void, SessionVerificationControllerProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acknowledgeVerificationRequestDetailsUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acknowledgeVerificationRequestDetailsUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acknowledgeVerificationRequestDetailsClosure: ((SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError>)?
|
||||||
|
|
||||||
|
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
acknowledgeVerificationRequestDetailsCallsCount += 1
|
||||||
|
acknowledgeVerificationRequestDetailsReceivedDetails = details
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.acknowledgeVerificationRequestDetailsReceivedInvocations.append(details)
|
||||||
|
}
|
||||||
|
if let acknowledgeVerificationRequestDetailsClosure = acknowledgeVerificationRequestDetailsClosure {
|
||||||
|
return await acknowledgeVerificationRequestDetailsClosure(details)
|
||||||
|
} else {
|
||||||
|
return acknowledgeVerificationRequestDetailsReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - acceptVerificationRequest
|
||||||
|
|
||||||
|
var acceptVerificationRequestUnderlyingCallsCount = 0
|
||||||
|
var acceptVerificationRequestCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acceptVerificationRequestCalled: Bool {
|
||||||
|
return acceptVerificationRequestCallsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var acceptVerificationRequestUnderlyingReturnValue: Result<Void, SessionVerificationControllerProxyError>!
|
||||||
|
var acceptVerificationRequestReturnValue: Result<Void, SessionVerificationControllerProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acceptVerificationRequestUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Void, SessionVerificationControllerProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acceptVerificationRequestUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acceptVerificationRequestUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acceptVerificationRequestUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acceptVerificationRequestClosure: (() async -> Result<Void, SessionVerificationControllerProxyError>)?
|
||||||
|
|
||||||
|
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
acceptVerificationRequestCallsCount += 1
|
||||||
|
if let acceptVerificationRequestClosure = acceptVerificationRequestClosure {
|
||||||
|
return await acceptVerificationRequestClosure()
|
||||||
|
} else {
|
||||||
|
return acceptVerificationRequestReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - requestVerification
|
//MARK: - requestVerification
|
||||||
|
|
||||||
var requestVerificationUnderlyingCallsCount = 0
|
var requestVerificationUnderlyingCallsCount = 0
|
||||||
|
|||||||
@@ -16529,6 +16529,92 @@ open class SessionVerificationControllerSDKMock: MatrixRustSDK.SessionVerificati
|
|||||||
|
|
||||||
fileprivate var pointer: UnsafeMutableRawPointer!
|
fileprivate var pointer: UnsafeMutableRawPointer!
|
||||||
|
|
||||||
|
//MARK: - acceptVerificationRequest
|
||||||
|
|
||||||
|
open var acceptVerificationRequestThrowableError: Error?
|
||||||
|
var acceptVerificationRequestUnderlyingCallsCount = 0
|
||||||
|
open var acceptVerificationRequestCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acceptVerificationRequestUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acceptVerificationRequestUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var acceptVerificationRequestCalled: Bool {
|
||||||
|
return acceptVerificationRequestCallsCount > 0
|
||||||
|
}
|
||||||
|
open var acceptVerificationRequestClosure: (() async throws -> Void)?
|
||||||
|
|
||||||
|
open override func acceptVerificationRequest() async throws {
|
||||||
|
if let error = acceptVerificationRequestThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
acceptVerificationRequestCallsCount += 1
|
||||||
|
try await acceptVerificationRequestClosure?()
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - acknowledgeVerificationRequest
|
||||||
|
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdThrowableError: Error?
|
||||||
|
var acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount = 0
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdCalled: Bool {
|
||||||
|
return acknowledgeVerificationRequestSenderIdFlowIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdReceivedArguments: (senderId: String, flowId: String)?
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdReceivedInvocations: [(senderId: String, flowId: String)] = []
|
||||||
|
open var acknowledgeVerificationRequestSenderIdFlowIdClosure: ((String, String) async throws -> Void)?
|
||||||
|
|
||||||
|
open override func acknowledgeVerificationRequest(senderId: String, flowId: String) async throws {
|
||||||
|
if let error = acknowledgeVerificationRequestSenderIdFlowIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdCallsCount += 1
|
||||||
|
acknowledgeVerificationRequestSenderIdFlowIdReceivedArguments = (senderId: senderId, flowId: flowId)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.acknowledgeVerificationRequestSenderIdFlowIdReceivedInvocations.append((senderId: senderId, flowId: flowId))
|
||||||
|
}
|
||||||
|
try await acknowledgeVerificationRequestSenderIdFlowIdClosure?(senderId, flowId)
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - approveVerification
|
//MARK: - approveVerification
|
||||||
|
|
||||||
open var approveVerificationThrowableError: Error?
|
open var approveVerificationThrowableError: Error?
|
||||||
@@ -16649,75 +16735,6 @@ open class SessionVerificationControllerSDKMock: MatrixRustSDK.SessionVerificati
|
|||||||
try await declineVerificationClosure?()
|
try await declineVerificationClosure?()
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - isVerified
|
|
||||||
|
|
||||||
open var isVerifiedThrowableError: Error?
|
|
||||||
var isVerifiedUnderlyingCallsCount = 0
|
|
||||||
open var isVerifiedCallsCount: Int {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return isVerifiedUnderlyingCallsCount
|
|
||||||
} else {
|
|
||||||
var returnValue: Int? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = isVerifiedUnderlyingCallsCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
isVerifiedUnderlyingCallsCount = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
isVerifiedUnderlyingCallsCount = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var isVerifiedCalled: Bool {
|
|
||||||
return isVerifiedCallsCount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var isVerifiedUnderlyingReturnValue: Bool!
|
|
||||||
open var isVerifiedReturnValue: Bool! {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return isVerifiedUnderlyingReturnValue
|
|
||||||
} else {
|
|
||||||
var returnValue: Bool? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = isVerifiedUnderlyingReturnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
isVerifiedUnderlyingReturnValue = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
isVerifiedUnderlyingReturnValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var isVerifiedClosure: (() async throws -> Bool)?
|
|
||||||
|
|
||||||
open override func isVerified() async throws -> Bool {
|
|
||||||
if let error = isVerifiedThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
isVerifiedCallsCount += 1
|
|
||||||
if let isVerifiedClosure = isVerifiedClosure {
|
|
||||||
return try await isVerifiedClosure()
|
|
||||||
} else {
|
|
||||||
return isVerifiedReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: - requestVerification
|
//MARK: - requestVerification
|
||||||
|
|
||||||
open var requestVerificationThrowableError: Error?
|
open var requestVerificationThrowableError: Error?
|
||||||
|
|||||||
@@ -16,16 +16,18 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
SessionVerificationEmoji(symbol: "🏁", description: "Flag"),
|
SessionVerificationEmoji(symbol: "🏁", description: "Flag"),
|
||||||
SessionVerificationEmoji(symbol: "🌏", description: "Globe")]
|
SessionVerificationEmoji(symbol: "🌏", description: "Globe")]
|
||||||
|
|
||||||
static func configureMock(callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> = .init(),
|
static func configureMock(actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> = .init(),
|
||||||
isVerified: Bool = false,
|
isVerified: Bool = false,
|
||||||
requestDelay: Duration = .seconds(1)) -> SessionVerificationControllerProxyMock {
|
requestDelay: Duration = .seconds(1)) -> SessionVerificationControllerProxyMock {
|
||||||
let mock = SessionVerificationControllerProxyMock()
|
let mock = SessionVerificationControllerProxyMock()
|
||||||
mock.underlyingCallbacks = callbacks
|
mock.underlyingActions = actions
|
||||||
|
|
||||||
|
mock.acknowledgeVerificationRequestDetailsReturnValue = .success(())
|
||||||
|
|
||||||
mock.requestVerificationClosure = { [unowned mock] in
|
mock.requestVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.acceptedVerificationRequest)
|
mock.actions.send(.acceptedVerificationRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
@@ -34,11 +36,11 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.startSasVerificationClosure = { [unowned mock] in
|
mock.startSasVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.startedSasVerification)
|
mock.actions.send(.startedSasVerification)
|
||||||
|
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.receivedVerificationData(emojis))
|
mock.actions.send(.receivedVerificationData(emojis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.approveVerificationClosure = { [unowned mock] in
|
mock.approveVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.finished)
|
mock.actions.send(.finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
@@ -57,7 +59,7 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.declineVerificationClosure = { [unowned mock] in
|
mock.declineVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.cancelled)
|
mock.actions.send(.cancelled)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
@@ -66,7 +68,7 @@ extension SessionVerificationControllerProxyMock {
|
|||||||
mock.cancelVerificationClosure = { [unowned mock] in
|
mock.cancelVerificationClosure = { [unowned mock] in
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
mock.callbacks.send(.cancelled)
|
mock.actions.send(.cancelled)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ enum A11yIdentifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct SessionVerificationScreen {
|
struct SessionVerificationScreen {
|
||||||
|
let acceptVerificationRequest = "session_verification-accept_verification_request"
|
||||||
|
let ignoreVerificationRequest = "session_verification-ignore_verification_request"
|
||||||
let requestVerification = "session_verification-request_verification"
|
let requestVerification = "session_verification-request_verification"
|
||||||
let startSasVerification = "session_verification-start_sas_verification"
|
let startSasVerification = "session_verification-start_sas_verification"
|
||||||
let acceptChallenge = "session_verification-accept_challenge"
|
let acceptChallenge = "session_verification-accept_challenge"
|
||||||
|
|||||||
+9
-1
@@ -6,14 +6,21 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import MatrixRustSDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum SessionVerificationScreenCoordinatorAction {
|
enum SessionVerificationScreenCoordinatorAction {
|
||||||
case done
|
case done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SessionVerificationScreenFlow {
|
||||||
|
case initiator
|
||||||
|
case responder(details: SessionVerificationRequestDetails)
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionVerificationScreenCoordinatorParameters {
|
struct SessionVerificationScreenCoordinatorParameters {
|
||||||
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||||
|
let flow: SessionVerificationScreenFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -27,7 +34,8 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(parameters: SessionVerificationScreenCoordinatorParameters) {
|
init(parameters: SessionVerificationScreenCoordinatorParameters) {
|
||||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy)
|
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy,
|
||||||
|
flow: parameters.flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|||||||
+62
-15
@@ -11,13 +11,61 @@ enum SessionVerificationScreenViewModelAction {
|
|||||||
case finished
|
case finished
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SessionVerificationScreenViewAction {
|
||||||
|
case acceptVerificationRequest
|
||||||
|
case ignoreVerificationRequest
|
||||||
|
case requestVerification
|
||||||
|
case startSasVerification
|
||||||
|
case restart
|
||||||
|
case accept
|
||||||
|
case decline
|
||||||
|
case done
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionVerificationScreenViewState: BindableState {
|
struct SessionVerificationScreenViewState: BindableState {
|
||||||
var verificationState: SessionVerificationScreenStateMachine.State = .initial
|
let flow: SessionVerificationScreenFlow
|
||||||
|
var verificationState: SessionVerificationScreenStateMachine.State
|
||||||
|
|
||||||
|
var headerImageName: String {
|
||||||
|
switch verificationState {
|
||||||
|
case .initial:
|
||||||
|
return "lock"
|
||||||
|
case .acceptingVerificationRequest:
|
||||||
|
return "hourglass"
|
||||||
|
case .requestingVerification:
|
||||||
|
return "hourglass"
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
return "face.smiling"
|
||||||
|
case .startingSasVerification:
|
||||||
|
return "hourglass"
|
||||||
|
case .sasVerificationStarted:
|
||||||
|
return "hourglass"
|
||||||
|
case .cancelling:
|
||||||
|
return "hourglass"
|
||||||
|
case .acceptingChallenge:
|
||||||
|
return "hourglass"
|
||||||
|
case .decliningChallenge:
|
||||||
|
return "hourglass"
|
||||||
|
case .showingChallenge:
|
||||||
|
return "face.smiling"
|
||||||
|
case .verified:
|
||||||
|
return "checkmark.shield"
|
||||||
|
case .cancelled:
|
||||||
|
return "exclamationmark.shield"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var title: String? {
|
var title: String? {
|
||||||
switch verificationState {
|
switch verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
return L10n.screenSessionVerificationOpenExistingSessionTitle
|
switch flow {
|
||||||
|
case .initiator:
|
||||||
|
return L10n.screenSessionVerificationOpenExistingSessionTitle
|
||||||
|
case .responder:
|
||||||
|
return L10n.screenSessionVerificationRequestTitle
|
||||||
|
}
|
||||||
|
case .acceptingVerificationRequest:
|
||||||
|
return L10n.screenSessionVerificationRequestTitle
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
return L10n.screenSessionVerificationWaitingToAcceptTitle
|
return L10n.screenSessionVerificationWaitingToAcceptTitle
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
@@ -31,13 +79,13 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
case .acceptingChallenge:
|
case .acceptingChallenge:
|
||||||
return L10n.screenSessionVerificationCompareEmojisTitle
|
return L10n.screenSessionVerificationCompareEmojisTitle
|
||||||
case .decliningChallenge:
|
case .decliningChallenge:
|
||||||
return nil
|
return L10n.screenSessionVerificationCompareEmojisTitle
|
||||||
case .verified:
|
case .verified:
|
||||||
return L10n.commonVerificationComplete
|
return L10n.commonVerificationComplete
|
||||||
case .cancelling:
|
case .cancelling:
|
||||||
return nil
|
return nil
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
return L10n.commonVerificationCancelled
|
return L10n.commonVerificationFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +96,14 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
var message: String {
|
var message: String {
|
||||||
switch verificationState {
|
switch verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
return L10n.screenSessionVerificationOpenExistingSessionSubtitle
|
switch flow {
|
||||||
|
case .initiator:
|
||||||
|
return L10n.screenSessionVerificationOpenExistingSessionSubtitle
|
||||||
|
case .responder:
|
||||||
|
return L10n.screenSessionVerificationRequestSubtitle
|
||||||
|
}
|
||||||
|
case .acceptingVerificationRequest:
|
||||||
|
return L10n.screenSessionVerificationRequestSubtitle
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
return L10n.screenSessionVerificationWaitingToAcceptSubtitle
|
return L10n.screenSessionVerificationWaitingToAcceptSubtitle
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
@@ -60,7 +115,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
case .acceptingChallenge:
|
case .acceptingChallenge:
|
||||||
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||||
case .decliningChallenge:
|
case .decliningChallenge:
|
||||||
return L10n.commonWaiting
|
return L10n.screenSessionVerificationCompareEmojisSubtitle
|
||||||
case .cancelling:
|
case .cancelling:
|
||||||
return L10n.commonWaiting
|
return L10n.commonWaiting
|
||||||
case .showingChallenge:
|
case .showingChallenge:
|
||||||
@@ -68,15 +123,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
|||||||
case .verified:
|
case .verified:
|
||||||
return L10n.screenSessionVerificationCompleteSubtitle
|
return L10n.screenSessionVerificationCompleteSubtitle
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
return L10n.screenSessionVerificationCancelledSubtitle
|
return L10n.screenSessionVerificationFailedSubtitle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SessionVerificationScreenViewAction {
|
|
||||||
case requestVerification
|
|
||||||
case startSasVerification
|
|
||||||
case restart
|
|
||||||
case accept
|
|
||||||
case decline
|
|
||||||
}
|
|
||||||
|
|||||||
+15
-4
@@ -13,6 +13,8 @@ class SessionVerificationScreenStateMachine {
|
|||||||
enum State: StateType {
|
enum State: StateType {
|
||||||
/// The initial state, before verification started
|
/// The initial state, before verification started
|
||||||
case initial
|
case initial
|
||||||
|
/// Accepting the remote verification request
|
||||||
|
case acceptingVerificationRequest
|
||||||
/// Waiting for verification acceptance
|
/// Waiting for verification acceptance
|
||||||
case requestingVerification
|
case requestingVerification
|
||||||
/// Verification request accepted. Waiting for start
|
/// Verification request accepted. Waiting for start
|
||||||
@@ -37,6 +39,8 @@ class SessionVerificationScreenStateMachine {
|
|||||||
|
|
||||||
/// Events that can be triggered on the SessionVerification state machine
|
/// Events that can be triggered on the SessionVerification state machine
|
||||||
enum Event: EventType {
|
enum Event: EventType {
|
||||||
|
/// Accept the remote verification request
|
||||||
|
case acceptVerificationRequest
|
||||||
/// Request verification
|
/// Request verification
|
||||||
case requestVerification
|
case requestVerification
|
||||||
/// The current verification request has been accepted
|
/// The current verification request has been accepted
|
||||||
@@ -69,16 +73,23 @@ class SessionVerificationScreenStateMachine {
|
|||||||
stateMachine.state
|
stateMachine.state
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init(state: State) {
|
||||||
stateMachine = StateMachine(state: .initial)
|
stateMachine = StateMachine(state: state)
|
||||||
configure()
|
configure()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configure() {
|
private func configure() {
|
||||||
|
stateMachine.addRoutes(event: .acceptVerificationRequest, transitions: [.initial => .acceptingVerificationRequest])
|
||||||
stateMachine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
stateMachine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
||||||
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.requestingVerification => .verificationRequestAccepted])
|
|
||||||
|
stateMachine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.acceptingVerificationRequest => .verificationRequestAccepted,
|
||||||
|
.requestingVerification => .verificationRequestAccepted])
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .startSasVerification, transitions: [.verificationRequestAccepted => .startingSasVerification])
|
stateMachine.addRoutes(event: .startSasVerification, transitions: [.verificationRequestAccepted => .startingSasVerification])
|
||||||
stateMachine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial])
|
|
||||||
|
stateMachine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial,
|
||||||
|
.acceptingVerificationRequest => .initial])
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
|
stateMachine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
|
||||||
|
|
||||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||||
|
|||||||
+45
-6
@@ -12,6 +12,7 @@ typealias SessionVerificationViewModelType = StateStoreViewModel<SessionVerifica
|
|||||||
|
|
||||||
class SessionVerificationScreenViewModel: SessionVerificationViewModelType, SessionVerificationScreenViewModelProtocol {
|
class SessionVerificationScreenViewModel: SessionVerificationViewModelType, SessionVerificationScreenViewModelProtocol {
|
||||||
private let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
private let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||||
|
private let flow: SessionVerificationScreenFlow
|
||||||
|
|
||||||
private var stateMachine: SessionVerificationScreenStateMachine
|
private var stateMachine: SessionVerificationScreenStateMachine
|
||||||
|
|
||||||
@@ -22,21 +23,25 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
||||||
|
flow: SessionVerificationScreenFlow,
|
||||||
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
|
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
|
||||||
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
|
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
|
||||||
|
self.flow = flow
|
||||||
|
|
||||||
stateMachine = SessionVerificationScreenStateMachine()
|
stateMachine = SessionVerificationScreenStateMachine(state: verificationState)
|
||||||
|
|
||||||
super.init(initialViewState: .init(verificationState: verificationState))
|
super.init(initialViewState: .init(flow: flow, verificationState: verificationState))
|
||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
|
|
||||||
sessionVerificationControllerProxy.callbacks
|
sessionVerificationControllerProxy.actions
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] callback in
|
.sink { [weak self] callback in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch callback {
|
switch callback {
|
||||||
|
case .receivedVerificationRequest:
|
||||||
|
break // Incoming verification requests are handled on the higher levels
|
||||||
case .acceptedVerificationRequest:
|
case .acceptedVerificationRequest:
|
||||||
self.stateMachine.processEvent(.didAcceptVerificationRequest)
|
self.stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
case .startedSasVerification:
|
case .startedSasVerification:
|
||||||
@@ -57,10 +62,20 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
if case .responder(let details) = flow {
|
||||||
|
Task {
|
||||||
|
await self.sessionVerificationControllerProxy.acknowledgeVerificationRequest(details: details)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: SessionVerificationScreenViewAction) {
|
override func process(viewAction: SessionVerificationScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
|
case .acceptVerificationRequest:
|
||||||
|
stateMachine.processEvent(.acceptVerificationRequest)
|
||||||
|
case .ignoreVerificationRequest:
|
||||||
|
actionsSubject.send(.finished)
|
||||||
case .requestVerification:
|
case .requestVerification:
|
||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
case .startSasVerification:
|
case .startSasVerification:
|
||||||
@@ -71,13 +86,16 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
stateMachine.processEvent(.acceptChallenge)
|
stateMachine.processEvent(.acceptChallenge)
|
||||||
case .decline:
|
case .decline:
|
||||||
stateMachine.processEvent(.declineChallenge)
|
stateMachine.processEvent(.declineChallenge)
|
||||||
|
case .done:
|
||||||
|
actionsSubject.send(.finished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
let uncancellableStates: [SessionVerificationScreenStateMachine.State] = [.initial, .verified, .cancelled]
|
switch stateMachine.state {
|
||||||
|
case .initial, .verified, .cancelled: // non-cancellable states
|
||||||
if !uncancellableStates.contains(stateMachine.state) {
|
return
|
||||||
|
default:
|
||||||
stateMachine.processEvent(.cancel)
|
stateMachine.processEvent(.cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +109,8 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
state.verificationState = context.toState
|
state.verificationState = context.toState
|
||||||
|
|
||||||
switch (context.fromState, context.event, context.toState) {
|
switch (context.fromState, context.event, context.toState) {
|
||||||
|
case (.initial, .acceptVerificationRequest, .acceptingVerificationRequest):
|
||||||
|
acceptVerificationRequest()
|
||||||
case (.initial, .requestVerification, .requestingVerification):
|
case (.initial, .requestVerification, .requestingVerification):
|
||||||
requestVerification()
|
requestVerification()
|
||||||
case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification):
|
case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification):
|
||||||
@@ -103,6 +123,10 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
cancelVerification()
|
cancelVerification()
|
||||||
case (_, _, .verified):
|
case (_, _, .verified):
|
||||||
actionsSubject.send(.finished)
|
actionsSubject.send(.finished)
|
||||||
|
case (.initial, _, .cancelled):
|
||||||
|
if case .responder = flow {
|
||||||
|
actionsSubject.send(.finished)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -113,6 +137,21 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func acceptVerificationRequest() {
|
||||||
|
Task {
|
||||||
|
guard case .responder = flow else {
|
||||||
|
fatalError("Incorrect API usage.")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await sessionVerificationControllerProxy.acceptVerificationRequest() {
|
||||||
|
case .success:
|
||||||
|
stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
|
case .failure:
|
||||||
|
stateMachine.processEvent(.didFail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func requestVerification() {
|
private func requestVerification() {
|
||||||
Task {
|
Task {
|
||||||
switch await sessionVerificationControllerProxy.requestVerification() {
|
switch await sessionVerificationControllerProxy.requestVerification() {
|
||||||
|
|||||||
+80
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
-67
@@ -5,6 +5,8 @@
|
|||||||
// Please see LICENSE in the repository root for full details.
|
// Please see LICENSE in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import MatrixRustSDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SessionVerificationScreen: View {
|
struct SessionVerificationScreen: View {
|
||||||
@@ -14,7 +16,6 @@ struct SessionVerificationScreen: View {
|
|||||||
FullscreenDialog {
|
FullscreenDialog {
|
||||||
VStack(spacing: 32) {
|
VStack(spacing: 32) {
|
||||||
screenHeader
|
screenHeader
|
||||||
Spacer()
|
|
||||||
mainContent
|
mainContent
|
||||||
}
|
}
|
||||||
} bottomContent: {
|
} bottomContent: {
|
||||||
@@ -27,33 +28,6 @@ struct SessionVerificationScreen: View {
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private var headerImageName: String {
|
|
||||||
switch context.viewState.verificationState {
|
|
||||||
case .initial:
|
|
||||||
return "lock"
|
|
||||||
case .cancelled:
|
|
||||||
return "exclamationmark.shield"
|
|
||||||
case .requestingVerification:
|
|
||||||
return "hourglass"
|
|
||||||
case .verificationRequestAccepted:
|
|
||||||
return "face.smiling"
|
|
||||||
case .startingSasVerification:
|
|
||||||
return "hourglass"
|
|
||||||
case .sasVerificationStarted:
|
|
||||||
return "hourglass"
|
|
||||||
case .cancelling:
|
|
||||||
return "hourglass"
|
|
||||||
case .acceptingChallenge:
|
|
||||||
return "hourglass"
|
|
||||||
case .decliningChallenge:
|
|
||||||
return "hourglass"
|
|
||||||
case .showingChallenge:
|
|
||||||
return "face.smiling"
|
|
||||||
case .verified:
|
|
||||||
return "checkmark.shield"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var screenHeader: some View {
|
private var screenHeader: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -61,7 +35,7 @@ struct SessionVerificationScreen: View {
|
|||||||
BigIcon(icon: \.lockSolid)
|
BigIcon(icon: \.lockSolid)
|
||||||
.padding(.bottom, 16)
|
.padding(.bottom, 16)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: headerImageName)
|
Image(systemName: context.viewState.headerImageName)
|
||||||
.bigIcon()
|
.bigIcon()
|
||||||
.padding(.bottom, 16)
|
.padding(.bottom, 16)
|
||||||
}
|
}
|
||||||
@@ -83,18 +57,17 @@ struct SessionVerificationScreen: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .showingChallenge(let emojis):
|
case .initial:
|
||||||
|
switch context.viewState.flow {
|
||||||
|
case .responder(let details):
|
||||||
|
SessionVerificationRequestDetailsView(details: details)
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
case .showingChallenge(let emojis), .acceptingChallenge(let emojis), .decliningChallenge(let emojis):
|
||||||
emojisPanel(with: emojis)
|
emojisPanel(with: emojis)
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
||||||
case .acceptingChallenge(let emojis):
|
|
||||||
emojisPanel(with: emojis)
|
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.emojiWrapper)
|
|
||||||
case .requestingVerification:
|
|
||||||
ProgressView()
|
|
||||||
.tint(.compound.textSecondary)
|
|
||||||
.scaleEffect(2)
|
|
||||||
default:
|
default:
|
||||||
// In All other cases, we just want an empty view
|
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,18 +92,41 @@ struct SessionVerificationScreen: View {
|
|||||||
private var actionButtons: some View {
|
private var actionButtons: some View {
|
||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
VStack(spacing: 32) {
|
switch context.viewState.flow {
|
||||||
|
case .initiator:
|
||||||
Button(L10n.actionStartVerification) {
|
Button(L10n.actionStartVerification) {
|
||||||
context.send(viewAction: .requestVerification)
|
context.send(viewAction: .requestVerification)
|
||||||
}
|
}
|
||||||
.buttonStyle(.compound(.primary))
|
.buttonStyle(.compound(.primary))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
|
||||||
|
case .responder:
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Button(L10n.actionStart) {
|
||||||
|
context.send(viewAction: .acceptVerificationRequest)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptVerificationRequest)
|
||||||
|
|
||||||
|
Button(L10n.actionIgnore) {
|
||||||
|
context.send(viewAction: .ignoreVerificationRequest)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.plain))
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.ignoreVerificationRequest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
Button(L10n.actionRetry) {
|
switch context.viewState.flow {
|
||||||
context.send(viewAction: .restart)
|
case .initiator:
|
||||||
|
Button(L10n.actionRetry) {
|
||||||
|
context.send(viewAction: .restart)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
case .responder:
|
||||||
|
Button(L10n.actionDone) {
|
||||||
|
context.send(viewAction: .done)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
}
|
}
|
||||||
.buttonStyle(.compound(.primary))
|
|
||||||
|
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
Button(L10n.actionStart) {
|
Button(L10n.actionStart) {
|
||||||
@@ -150,31 +146,15 @@ struct SessionVerificationScreen: View {
|
|||||||
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
||||||
context.send(viewAction: .decline)
|
context.send(viewAction: .decline)
|
||||||
}
|
}
|
||||||
.font(.compound.bodyLGSemibold)
|
.buttonStyle(.compound(.plain))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .acceptingChallenge:
|
case .acceptingVerificationRequest, .acceptingChallenge, .decliningChallenge, .requestingVerification:
|
||||||
VStack(spacing: 32) {
|
Button(L10n.screenIdentityWaitingOnOtherDevice) { }
|
||||||
Button { context.send(viewAction: .accept) } label: {
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
ProgressView()
|
|
||||||
.tint(.compound.textOnSolidPrimary)
|
|
||||||
Text(L10n.screenSessionVerificationTheyMatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(.compound(.primary))
|
.buttonStyle(.compound(.primary))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
|
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
|
||||||
Button(L10n.screenSessionVerificationTheyDontMatch) {
|
|
||||||
context.send(viewAction: .decline)
|
|
||||||
}
|
|
||||||
.font(.compound.bodyLGSemibold)
|
|
||||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
|
|
||||||
.disabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@@ -196,27 +176,50 @@ struct SessionVerificationScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
|
||||||
|
|
||||||
struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
sessionVerificationScreen(state: .initial)
|
sessionVerificationScreen(state: .initial)
|
||||||
.previewDisplayName("Initial")
|
.previewDisplayName("Initial - Initiator")
|
||||||
|
|
||||||
|
let details = SessionVerificationRequestDetails(senderID: "@bob:matrix.org",
|
||||||
|
flowID: "123",
|
||||||
|
deviceID: "CODEMISTAKE",
|
||||||
|
displayName: "Bob's Element X iOS",
|
||||||
|
firstSeenDate: .init(timeIntervalSince1970: 0))
|
||||||
|
sessionVerificationScreen(state: .initial, flow: .responder(details: details))
|
||||||
|
.previewDisplayName("Initial - Responder")
|
||||||
|
|
||||||
|
sessionVerificationScreen(state: .acceptingVerificationRequest)
|
||||||
|
.previewDisplayName("Accepting Verification Request")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .requestingVerification)
|
sessionVerificationScreen(state: .requestingVerification)
|
||||||
.previewDisplayName("Requesting Verification")
|
.previewDisplayName("Requesting Verification")
|
||||||
sessionVerificationScreen(state: .verificationRequestAccepted)
|
sessionVerificationScreen(state: .verificationRequestAccepted)
|
||||||
.previewDisplayName("Request Accepted")
|
.previewDisplayName("Request Accepted")
|
||||||
sessionVerificationScreen(state: .cancelled)
|
|
||||||
.previewDisplayName("Cancelled")
|
sessionVerificationScreen(state: .startingSasVerification)
|
||||||
|
.previewDisplayName("Starting SAS Verification")
|
||||||
|
sessionVerificationScreen(state: .sasVerificationStarted)
|
||||||
|
.previewDisplayName("SAS Verification started")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
sessionVerificationScreen(state: .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||||
.previewDisplayName("Showing Challenge")
|
.previewDisplayName("Showing Challenge")
|
||||||
|
sessionVerificationScreen(state: .acceptingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||||
|
.previewDisplayName("Accepting Challenge")
|
||||||
|
sessionVerificationScreen(state: .decliningChallenge(emojis: SessionVerificationControllerProxyMock.emojis))
|
||||||
|
.previewDisplayName("Declining Challenge")
|
||||||
|
|
||||||
sessionVerificationScreen(state: .verified)
|
sessionVerificationScreen(state: .verified)
|
||||||
.previewDisplayName("Verified")
|
.previewDisplayName("Verified")
|
||||||
|
|
||||||
|
sessionVerificationScreen(state: .cancelled)
|
||||||
|
.previewDisplayName("Cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State) -> some View {
|
static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State,
|
||||||
|
flow: SessionVerificationScreenFlow = .initiator) -> some View {
|
||||||
let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(),
|
let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(),
|
||||||
|
flow: flow,
|
||||||
verificationState: state)
|
verificationState: state)
|
||||||
|
|
||||||
return SessionVerificationScreen(context: viewModel.context)
|
return SessionVerificationScreen(context: viewModel.context)
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class TimelineInteractionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
let secureBackupController: SecureBackupControllerProtocol
|
let secureBackupController: SecureBackupControllerProtocol
|
||||||
|
|
||||||
|
private(set) var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||||
|
|
||||||
private static var roomCreationPowerLevelOverrides: PowerLevels {
|
private static var roomCreationPowerLevelOverrides: PowerLevels {
|
||||||
.init(usersDefault: nil,
|
.init(usersDefault: nil,
|
||||||
eventsDefault: nil,
|
eventsDefault: nil,
|
||||||
@@ -157,7 +159,16 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
updateVerificationState(client.encryption().verificationState())
|
updateVerificationState(client.encryption().verificationState())
|
||||||
|
|
||||||
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
|
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
|
||||||
self?.updateVerificationState(verificationState)
|
guard let self else { return }
|
||||||
|
|
||||||
|
updateVerificationState(verificationState)
|
||||||
|
|
||||||
|
// The session verification controller requires the user's identity which
|
||||||
|
// isn't available before a keys query response. Use the verification
|
||||||
|
// state updates as an aproximation for when that happens.
|
||||||
|
Task {
|
||||||
|
await self.buildSessionVerificationControllerProxyIfPossible(verificationState: verificationState)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sendQueueListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SendQueueRoomErrorListenerProxy { [weak self] roomID, error in
|
sendQueueListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SendQueueRoomErrorListenerProxy { [weak self] roomID, error in
|
||||||
@@ -484,7 +495,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
|
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
|
||||||
do {
|
do {
|
||||||
let roomPreview = try await client.getRoomPreviewFromRoomId(roomId: identifier, viaServers: via)
|
let roomPreview = try await client.getRoomPreviewFromRoomId(roomId: identifier, viaServers: via)
|
||||||
return .success(.init(roomPreview))
|
let roomPreviewInfo = try roomPreview.info()
|
||||||
|
return .success(.init(roomPreviewInfo))
|
||||||
} catch let error as ClientError where error.code == .forbidden {
|
} catch let error as ClientError where error.code == .forbidden {
|
||||||
return .failure(.roomPreviewIsPrivate)
|
return .failure(.roomPreviewIsPrivate)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -561,16 +573,6 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
|
||||||
do {
|
|
||||||
let sessionVerificationController = try await client.getSessionVerificationController()
|
|
||||||
return .success(SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController))
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
|
|
||||||
return .failure(.sdkError(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logout() async -> URL? {
|
func logout() async -> URL? {
|
||||||
do {
|
do {
|
||||||
return try await client.logout().flatMap(URL.init(string:))
|
return try await client.logout().flatMap(URL.init(string:))
|
||||||
@@ -729,6 +731,19 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
verificationStateSubject.send(verificationState)
|
verificationStateSubject.send(verificationState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func buildSessionVerificationControllerProxyIfPossible(verificationState: VerificationState) async {
|
||||||
|
guard sessionVerificationController == nil, verificationState != .unknown else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let sessionVerificationController = try await client.getSessionVerificationController()
|
||||||
|
self.sessionVerificationController = SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func loadUserAvatarURLFromCache() {
|
private func loadUserAvatarURLFromCache() {
|
||||||
loadCachedAvatarURLTask = Task {
|
loadCachedAvatarURLTask = Task {
|
||||||
let urlString = await Task.dispatch(on: clientQueue) {
|
let urlString = await Task.dispatch(on: clientQueue) {
|
||||||
@@ -1079,17 +1094,17 @@ private class SendQueueRoomErrorListenerProxy: SendQueueRoomErrorListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension RoomPreviewDetails {
|
private extension RoomPreviewDetails {
|
||||||
init(_ roomPreview: RoomPreview) {
|
init(_ roomPreviewInfo: RoomPreviewInfo) {
|
||||||
self = RoomPreviewDetails(roomID: roomPreview.roomId,
|
self = RoomPreviewDetails(roomID: roomPreviewInfo.roomId,
|
||||||
name: roomPreview.name,
|
name: roomPreviewInfo.name,
|
||||||
canonicalAlias: roomPreview.canonicalAlias,
|
canonicalAlias: roomPreviewInfo.canonicalAlias,
|
||||||
topic: roomPreview.topic,
|
topic: roomPreviewInfo.topic,
|
||||||
avatarURL: roomPreview.avatarUrl.flatMap(URL.init(string:)),
|
avatarURL: roomPreviewInfo.avatarUrl.flatMap(URL.init(string:)),
|
||||||
memberCount: UInt(roomPreview.numJoinedMembers),
|
memberCount: UInt(roomPreviewInfo.numJoinedMembers),
|
||||||
isHistoryWorldReadable: roomPreview.isHistoryWorldReadable,
|
isHistoryWorldReadable: roomPreviewInfo.isHistoryWorldReadable,
|
||||||
isJoined: roomPreview.isJoined,
|
isJoined: roomPreviewInfo.membership == .joined,
|
||||||
isInvited: roomPreview.isInvited,
|
isInvited: roomPreviewInfo.membership == .invited,
|
||||||
isPublic: roomPreview.isPublic,
|
isPublic: roomPreviewInfo.joinRule == .public,
|
||||||
canKnock: roomPreview.canKnock)
|
canKnock: roomPreviewInfo.joinRule == .knock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
var secureBackupController: SecureBackupControllerProtocol { get }
|
var secureBackupController: SecureBackupControllerProtocol { get }
|
||||||
|
|
||||||
|
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
|
||||||
|
|
||||||
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
||||||
|
|
||||||
func startSync()
|
func startSync()
|
||||||
@@ -156,8 +158,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
func removeUserAvatar() async -> Result<Void, ClientProxyError>
|
func removeUserAvatar() async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
|
|
||||||
|
|
||||||
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
|
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
func logout() async -> URL?
|
func logout() async -> URL?
|
||||||
|
|||||||
@@ -246,10 +246,8 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
// This gets called for no reason on simulators, where CallKit
|
// This gets called for no reason on simulators, where CallKit
|
||||||
// isn't even supported. Ignore
|
// isn't even supported, ignore it.
|
||||||
return
|
#else
|
||||||
#endif
|
|
||||||
|
|
||||||
if let ongoingCallID {
|
if let ongoingCallID {
|
||||||
actionsSubject.send(.endCall(roomID: ongoingCallID.roomID))
|
actionsSubject.send(.endCall(roomID: ongoingCallID.roomID))
|
||||||
}
|
}
|
||||||
@@ -257,6 +255,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
tearDownCallSession(sendEndCallAction: false)
|
tearDownCallSession(sendEndCallAction: false)
|
||||||
|
|
||||||
action.fulfill()
|
action.fulfill()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|||||||
+68
-8
@@ -18,6 +18,10 @@ private class WeakSessionVerificationControllerProxy: SessionVerificationControl
|
|||||||
|
|
||||||
// MARK: - SessionVerificationControllerDelegate
|
// MARK: - SessionVerificationControllerDelegate
|
||||||
|
|
||||||
|
func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||||
|
proxy?.didReceiveVerificationRequest(details: details)
|
||||||
|
}
|
||||||
|
|
||||||
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
|
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
|
||||||
switch data {
|
switch data {
|
||||||
// We can handle only emojis for now
|
// We can handle only emojis for now
|
||||||
@@ -54,86 +58,142 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
|
|||||||
|
|
||||||
init(sessionVerificationController: SessionVerificationController) {
|
init(sessionVerificationController: SessionVerificationController) {
|
||||||
self.sessionVerificationController = sessionVerificationController
|
self.sessionVerificationController = sessionVerificationController
|
||||||
|
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
sessionVerificationController.setDelegate(delegate: nil)
|
sessionVerificationController.setDelegate(delegate: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<SessionVerificationControllerProxyCallback, Never>()
|
let actions = PassthroughSubject<SessionVerificationControllerProxyAction, Never>()
|
||||||
|
|
||||||
|
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Acknowledging verification request")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await sessionVerificationController.acknowledgeVerificationRequest(senderId: details.senderID, flowId: details.flowID)
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||||
|
return .failure(.failedAcknowledgingVerificationRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Accepting verification request")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await sessionVerificationController.acceptVerificationRequest()
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||||
|
return .failure(.failedAcceptingVerificationRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
MXLog.info("Requesting session verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.requestVerification()
|
try await sessionVerificationController.requestVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||||
return .failure(.failedRequestingVerification)
|
return .failure(.failedRequestingVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Starting SAS verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.startSasVerification()
|
try await sessionVerificationController.startSasVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed starting SAS verification with error: \(error)")
|
||||||
return .failure(.failedStartingSasVerification)
|
return .failure(.failedStartingSasVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Approving verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.approveVerification()
|
try await sessionVerificationController.approveVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed approving verification with error: \(error)")
|
||||||
return .failure(.failedApprovingVerification)
|
return .failure(.failedApprovingVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Declining verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.declineVerification()
|
try await sessionVerificationController.declineVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed declining verification with error: \(error)")
|
||||||
return .failure(.failedDecliningVerification)
|
return .failure(.failedDecliningVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
MXLog.info("Cancelling verification")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await sessionVerificationController.cancelVerification()
|
try await sessionVerificationController.cancelVerification()
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
|
MXLog.error("Failed cancelling verification with error: \(error)")
|
||||||
return .failure(.failedCancellingVerification)
|
return .failure(.failedCancellingVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
fileprivate func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||||
|
MXLog.info("Received verification request \(details)")
|
||||||
|
|
||||||
|
let details = SessionVerificationRequestDetails(senderID: details.senderId,
|
||||||
|
flowID: details.flowId,
|
||||||
|
deviceID: details.deviceId,
|
||||||
|
displayName: details.displayName,
|
||||||
|
firstSeenDate: Date(timeIntervalSince1970: TimeInterval(details.firstSeenTimestamp / 1000)))
|
||||||
|
|
||||||
|
actions.send(.receivedVerificationRequest(details: details))
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func didAcceptVerificationRequest() {
|
fileprivate func didAcceptVerificationRequest() {
|
||||||
callbacks.send(.acceptedVerificationRequest)
|
MXLog.info("Accepted verification request")
|
||||||
|
|
||||||
|
actions.send(.acceptedVerificationRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didStartSasVerification() {
|
fileprivate func didStartSasVerification() {
|
||||||
callbacks.send(.startedSasVerification)
|
MXLog.info("Started SAS verification")
|
||||||
|
|
||||||
|
actions.send(.startedSasVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
||||||
callbacks.send(.receivedVerificationData(data.map { emoji in
|
MXLog.info("Received verification data")
|
||||||
|
|
||||||
|
actions.send(.receivedVerificationData(data.map { emoji in
|
||||||
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didFail() {
|
fileprivate func didFail() {
|
||||||
callbacks.send(.failed)
|
actions.send(.failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didFinish() {
|
fileprivate func didFinish() {
|
||||||
callbacks.send(.finished)
|
actions.send(.finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func didCancel() {
|
fileprivate func didCancel() {
|
||||||
callbacks.send(.cancelled)
|
actions.send(.cancelled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -7,8 +7,11 @@
|
|||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
enum SessionVerificationControllerProxyError: Error {
|
enum SessionVerificationControllerProxyError: Error {
|
||||||
|
case failedAcknowledgingVerificationRequest
|
||||||
|
case failedAcceptingVerificationRequest
|
||||||
case failedRequestingVerification
|
case failedRequestingVerification
|
||||||
case failedStartingSasVerification
|
case failedStartingSasVerification
|
||||||
case failedApprovingVerification
|
case failedApprovingVerification
|
||||||
@@ -16,7 +19,8 @@ enum SessionVerificationControllerProxyError: Error {
|
|||||||
case failedCancellingVerification
|
case failedCancellingVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SessionVerificationControllerProxyCallback {
|
enum SessionVerificationControllerProxyAction {
|
||||||
|
case receivedVerificationRequest(details: SessionVerificationRequestDetails)
|
||||||
case acceptedVerificationRequest
|
case acceptedVerificationRequest
|
||||||
case startedSasVerification
|
case startedSasVerification
|
||||||
case receivedVerificationData([SessionVerificationEmoji])
|
case receivedVerificationData([SessionVerificationEmoji])
|
||||||
@@ -25,6 +29,14 @@ enum SessionVerificationControllerProxyCallback {
|
|||||||
case failed
|
case failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SessionVerificationRequestDetails {
|
||||||
|
let senderID: String
|
||||||
|
let flowID: String
|
||||||
|
let deviceID: String
|
||||||
|
let displayName: String?
|
||||||
|
let firstSeenDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionVerificationEmoji: Hashable {
|
struct SessionVerificationEmoji: Hashable {
|
||||||
let symbol: String
|
let symbol: String
|
||||||
let description: String
|
let description: String
|
||||||
@@ -36,7 +48,11 @@ struct SessionVerificationEmoji: Hashable {
|
|||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
protocol SessionVerificationControllerProxyProtocol {
|
protocol SessionVerificationControllerProxyProtocol {
|
||||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> { get }
|
var actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> { get }
|
||||||
|
|
||||||
|
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
|
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
|
|||||||
@@ -519,7 +519,8 @@ class MockScreen: Identifiable {
|
|||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
case .sessionVerification:
|
case .sessionVerification:
|
||||||
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
|
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
|
||||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy)
|
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy,
|
||||||
|
flow: .initiator)
|
||||||
return SessionVerificationScreenCoordinator(parameters: parameters)
|
return SessionVerificationScreenCoordinator(parameters: parameters)
|
||||||
case .userSessionScreen, .userSessionScreenReply:
|
case .userSessionScreen, .userSessionScreenReply:
|
||||||
let appSettings: AppSettings = ServiceLocator.shared.settings
|
let appSettings: AppSettings = ServiceLocator.shared.settings
|
||||||
|
|||||||
@@ -755,6 +755,12 @@ extension PreviewTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_sessionVerificationRequestDetailsView() {
|
||||||
|
for preview in SessionVerificationRequestDetailsView_Previews._allPreviews {
|
||||||
|
assertSnapshots(matching: preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func test_sessionVerification() {
|
func test_sessionVerification() {
|
||||||
for preview in SessionVerification_Previews._allPreviews {
|
for preview in SessionVerification_Previews._allPreviews {
|
||||||
assertSnapshots(matching: preview)
|
assertSnapshots(matching: preview)
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0df8343fdaca7ed2ffa9b81b493cb1a5ab896d0f2cef08f83c0721815eb6c59d
|
oid sha256:2642ca05da869cf59a8c275465b67e43db8d01f7d984543ee4442a1a72fee1c2
|
||||||
size 133003
|
size 140623
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0511b9ba854183d086141e097e2cb88e39af7225d235b136e23676dde7447e9b
|
oid sha256:77d646f5b1e3f8377c674eb61b184670ba3e6e5eb26e16644a1271ec75d79cbb
|
||||||
size 179038
|
size 194850
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5e02892489581a9b285085fcda484561ff2f88e552934d6fdf3b78c9e53d8d04
|
oid sha256:57ba61f6ee8a7d6a382851bfe161d922a8179a58d4ddd191a08eb9891ab8a30b
|
||||||
size 88970
|
size 98103
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e9ad705443a5b1fdb27deae753c0fc4ffdddfa24316c6934b1e9ba1c9a2c250a
|
oid sha256:ea9ca53d25dd7f8fbd4453bca7dc02ffde0fd5bc43c0f183489080c5804fbc7c
|
||||||
size 137811
|
size 153579
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:42456c30e4c48f49498df5fb78543864ba73475b0519bb845fd8aea2b2711d2e
|
oid sha256:ffd720a584f845b15c8174adddb75e35f9e6351e765a70bd014d7ed56b49b6ca
|
||||||
size 122723
|
size 122368
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d812c0830f2b0b88db0eba82fcbceb9b4c5698e7c55ad0e9e7fc5a9c83e96249
|
oid sha256:f2c1eae53b77cd9dea156c19c53fbbfe28686b79d598ef81c462eccf7009dfa2
|
||||||
size 123915
|
size 123564
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:af3a401be4780e063d611293d06988524e07ea45bd4558d51d0f4f0a937ce531
|
oid sha256:27c5dcb193ec709f28df822e0caf323f2ec813cbb04eee7e8c92f97066a46f5f
|
||||||
size 150029
|
size 149956
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1500a5f539dc2ec3faae11c0d5a707f4329a15814d686de570baa901e1420fdf
|
oid sha256:624079d169186d352d6bc540df12a7b3f161d0653dc6659048027eef71f1e0d1
|
||||||
size 151948
|
size 151879
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d1b0c218ffb7143d8da4567a02fa0741a94942449dd00c065b7415978a100e6d
|
oid sha256:f93b129a941aa558325a7124f9a72ca4c6a722d67b6325cdb38f6b0c64a6af6b
|
||||||
size 76017
|
size 76045
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6ba57bc91e2ca37583848b7127ebb0e6efc9f932cee136e596af6bd9f762490b
|
oid sha256:9d8b72b3b29a6d7ae5302fbff84c09e5364fc28f683fbc1d917a20a213accc06
|
||||||
size 77531
|
size 77564
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3dee45d086663e3348733885a809a68e3f86cc33c70ca4e49676daa04f8dcd02
|
oid sha256:ccd9db4b98e04771c641c09e7d8c2bedcffd1eace9f2cb50628ac77051f81cd9
|
||||||
size 100956
|
size 101454
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d6506e821995ed525758a557e0dc64c57c3331267c37cd4bcea742fc244cc435
|
oid sha256:ce1816cf9eef04e5435a8a74c35d61fc8c8b5d4493c4577bad8bc267a0b00ea1
|
||||||
size 106296
|
size 106794
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:78da542fab77c7138034ee29b3985d0b6198cb79600ec22679a9aa8cf6b7de7b
|
||||||
|
size 161895
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f1e631e093fcbae1de84ecf012e2dad0feace858d4825f839adc9feae8d497ef
|
||||||
|
size 96902
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:94e76332e1728aac71a9684ee484eae180e84c88ea616798df0c5d883ac1e644
|
oid sha256:5e2aacff101d124325e3966e44c22fa02ef1a307c23a723d6622d4ee64412c21
|
||||||
size 93529
|
size 95267
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:78da542fab77c7138034ee29b3985d0b6198cb79600ec22679a9aa8cf6b7de7b
|
||||||
|
size 161895
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:de6d5e5d95b4fee23d4b0a7fb771752ac426c6bf81499b9c6e3a357a10d2c2f8
|
||||||
|
size 125144
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6a87fcdb4512157795aeaf16e9a94cab6a18677687f0015edbc0a696fc8a5215
|
oid sha256:b6b0a9b619f1dc6f02399aa4976c3dc19796d987e2a41ed37e5c2a449c85c11d
|
||||||
size 95084
|
size 98780
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:356ec08a1adbca9186d746bb84c3597ec1f7e324bcc975186b9233ee3d3813d5
|
||||||
|
size 72478
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:90f39070e7c681d4bce49d494fe4cd967092f68dd16767db450d275993ded1cc
|
oid sha256:c8741e91150e57869e2f8d94a13360e57b4d243e9054711d2335a514e5d9029b
|
||||||
size 165443
|
size 165427
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:356ec08a1adbca9186d746bb84c3597ec1f7e324bcc975186b9233ee3d3813d5
|
||||||
|
size 72478
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:28e89a7b00f5b07cc1ebef0034a38d2bfebdce8d8e924c20252877d8f4643d44
|
||||||
|
size 180153
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:55e04390c635595b18a60905e2ebffef065d27a88023484cd1a25e9a683ea378
|
||||||
|
size 108580
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ec94f9759e1a8f8bba3feb88d0e35fceff8f4bd69b3860034cfd3596310f02cc
|
oid sha256:2c313669307a1f85aef0d367a1284685eee2ed0f07b4b0fcedfa3728f27b869f
|
||||||
size 105774
|
size 108998
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:28e89a7b00f5b07cc1ebef0034a38d2bfebdce8d8e924c20252877d8f4643d44
|
||||||
|
size 180153
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:bad67fb36d792965b12fe728fae7c9664d4b8031c554117162fadd4b74502045
|
||||||
|
size 146678
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:802b547e9461a061bc51df508d8474d59d0175f35da057469dbfa2e8b18414ae
|
oid sha256:fa39a6dc77a211f757f20bdfa63ef04bfd79dd3d3b69a5bb8e3d9fc454a18e06
|
||||||
size 106580
|
size 111330
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:21a964833655bb12bbbb2ae486961cad6f3046bd3c2c6a2127c99821b14e0dcd
|
||||||
|
size 72175
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9b083b28e3dd51259f7fcae01f7bd482c684b3458d30dc464aba0d4cc4094b51
|
oid sha256:f25fc8b1b70ac00f2eb1cf5b6e55c4c3b5e509b3ed2e03458c99c36fa5abb30d
|
||||||
size 184847
|
size 184353
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:21a964833655bb12bbbb2ae486961cad6f3046bd3c2c6a2127c99821b14e0dcd
|
||||||
|
size 72175
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9d039980194a63eb87c15b4179b6132cb98cd4b9c069a90483cc2761d771c7c6
|
||||||
|
size 112841
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d8df3040ac64296541ce088a3c89d173874981fb413ec12168d4e6c976364c4a
|
||||||
|
size 54304
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:40738872c62954bd5f86d4241f3128472c8328c3295fd3f6f46bfff2c38717f4
|
oid sha256:d3eee192ba2ba7f7a04dc6b30ebd453ff037fd93cbdbc5f7385749f598718d2c
|
||||||
size 52459
|
size 52650
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9d039980194a63eb87c15b4179b6132cb98cd4b9c069a90483cc2761d771c7c6
|
||||||
|
size 112841
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0b4ab3956d6cdaab31410c633ad87f68b95992ed7f2f1fdcfe47d264833d925a
|
||||||
|
size 76508
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2b796634f5f0071e70fadfa6b13df5cc06425a42b450af365e91a4e14c0b6636
|
oid sha256:c24f2aeb174dbaad526a45ea0aadc4a88e96af5acee0395dce735437f33fed37
|
||||||
size 52007
|
size 56314
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a6f9ffc43e63a49254324af496f8aadda255e84f5ec97aae0f1a05542c8c1a5b
|
||||||
|
size 32123
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:71eabdfd2d5cc1a86eb42043d1aa5e2a1c401722ecacdab7fd70e45898051c2c
|
oid sha256:d0c95e9f0f51109b8fef6f771a8fe27bcd33eac2e244eefbcad2f223c8228fc2
|
||||||
size 114898
|
size 115566
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a6f9ffc43e63a49254324af496f8aadda255e84f5ec97aae0f1a05542c8c1a5b
|
||||||
|
size 32123
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:251fc57d77b23ca84e5b659dfb3cf51effba91e496566447dd5d5956f19eb81a
|
||||||
|
size 134250
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:61faed09ca05772707dc737fe7c9fbe7c59ddd5852b607f7df13a43071d1e82c
|
||||||
|
size 74935
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1c6060f6ce1cd9d447f9d642dd2a007f1ddea9d56b877a567691f885e742e5aa
|
oid sha256:4cb1087bf68fc7dc34b5c26653ee7ddf3859f2f1de22df1513c1d7b944d68feb
|
||||||
size 71244
|
size 71714
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:251fc57d77b23ca84e5b659dfb3cf51effba91e496566447dd5d5956f19eb81a
|
||||||
|
size 134250
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2a7ffbb91030a6ba3d0e064bd6cbaaa0f109915cc4f04ceced27b0185b6a541f
|
||||||
|
size 101933
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1c1f50bc1cd49c71bed1e48f540497f82fca9e5ae2130114ed2257e42065a9f9
|
oid sha256:c40c972bb56a40bbb70725248eee5ff642e9cced47e1b794128f1c2468309a84
|
||||||
size 69054
|
size 79541
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1cd1abe33be884130a8475ada0aab30cadd3ce7b1262e316bd0b4552aacead14
|
||||||
|
size 33085
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:36baf8914073371e25859a9dcd82bd9e461efebc4a8905fea7b28eb1da84ae08
|
oid sha256:91e8118f1315c91e4c4968fd6662253f0da5785dbf54a1c32aeb1b91a1785de9
|
||||||
size 135368
|
size 134584
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1cd1abe33be884130a8475ada0aab30cadd3ce7b1262e316bd0b4552aacead14
|
||||||
|
size 33085
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:db9bed799aae9732c42fc1361e72064f0b25e90abb4d8d07ec9cdc8441c8481e
|
||||||
|
size 92806
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6d9d00be62424b27994eff708c279596bd6e3e64cdf733c9e350a4c21247db4c
|
||||||
|
size 101514
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c77f8338cafa5cb5f14fdc64e9855a78088bffc7d0017ccea69035417532693b
|
||||||
|
size 50064
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:fe6709b8a05591a74557ed266f980dfb9c8fcfd7b4d2ed94b5cd35dd692dd45e
|
||||||
|
size 59494
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0f3849e4c3756976f3a618f5e865abbf3541222785b40c8da614c788c83cb4d9
|
oid sha256:c41e07b1476f8742d3d0bf6eb63bf03942715d40437082b9a3c6cb322742e860
|
||||||
size 139468
|
size 136314
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3b2d5bc69c3f0391f9981acb4f01bc327e4bf3a03a0a004185b343b725201b96
|
oid sha256:2616b3ce1d12360d6c2d151d0647989bdd1ee452ea2efa6c7a24c25e8382c136
|
||||||
size 177408
|
size 167378
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:86df341a29d0b0f1a284cc28f280d6c54d8d539f209270cf3fcc5e0b3b3da613
|
oid sha256:8e0ace29cb70b56c0a4b23a44fa2a019036a6c29b8e5fcf5e72aa04c1e9cfeaa
|
||||||
size 143371
|
size 138956
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dd4e597b19ea7d5132e91427ec09ec980f4a485aea58f219a560c82003280e0a
|
oid sha256:40a328061699a3e9435f799bf20b009eaa48f66f6028037804f0435b16501fd5
|
||||||
size 182727
|
size 172709
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:86b80fb5bb11af1953261fcbe366b85d5d6486e6860884f546a8541212cda0f1
|
oid sha256:7be6325cac7408df468f547a3cc1ef241c24efb652160fde4150e97109b2753c
|
||||||
size 122770
|
size 119761
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:49d38ecb60a832ba39029a4e8f211439aa4c3948112fbdfd51e71538bb1d0fd1
|
oid sha256:9ab4ce7eee2d029601e4d45b92aaac783df97a9b3ecb80b208a3d9cb2e9a1ce0
|
||||||
size 155107
|
size 144339
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:08b4681246a562c90a02a189663a7246fe135d037a7e5a0a1f5b4713a420fea1
|
oid sha256:c548364f9ad63a7d7c1d53e53600572c3e1477b3fa1ea13d4b941a73cda76a8a
|
||||||
size 86484
|
size 85908
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8ab61d812816d065aa8903c69df0459bba1142721cc9eef9e39ba05f7f505b66
|
oid sha256:ccf7b17e2bad6bbce72d5b29e32a309865b2b2b9cb76a5af2447b675613b14c4
|
||||||
size 85240
|
size 89009
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3df7a0c44ac8ceeb5509c03f882ccaa57b5bb7fb6d54fa7f7f0ddb2c5be6d6ab
|
oid sha256:7970d3cce906d1e1947407d7a9de5e8994069bd2b191ef3dc7c0a6b6b889d3bf
|
||||||
size 87190
|
size 93417
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:21932c55d7c3e55ce291c1ea410011a2b1b9647cd4e90d9295cefb85e74e8025
|
oid sha256:8b375d9b216880cd6feea614a8ae9f722d1705e31e443d6c1620c20edb4a1a89
|
||||||
size 84710
|
size 83942
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e91b2715da11e04376bf99a910f9b441aa3e21d7fc9e4567b0667c84f8e85b3d
|
oid sha256:bf16f963d57f00a55133201b3fadd5820dc22d9af158d0d2163b088b675fe918
|
||||||
size 83835
|
size 84194
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b58d6f9e5d1a4cc6d337634f96e2731fa9dea1cb128e2634d2ff2dc5858eb98b
|
oid sha256:cd87a89399242622d8176b1f4eb43f72b1f51314b0b8a01f0b5ac060516e65b3
|
||||||
size 65167
|
size 64449
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9728cb4db3d31f4b4c2a718c84cc8524aa18c4abdc5a7557099c82bd92c50726
|
oid sha256:f7f4dd44b1ca63ee19ab316d3dfa4da2fa3dc65bbc875a94659074b7d454d4bb
|
||||||
size 57405
|
size 56996
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8d9c8b24cf3351eb186348ac79da58bbd3d54682cb37084b5f923887e8bdf8ed
|
oid sha256:d6dbe259250888fe47d35e40c377a88032e4cde2d1fdc5cbe7395afdd304cbae
|
||||||
size 148162
|
size 147532
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:78ba7b00929594e231aea48863e5224e864717e35a319a573d73af850fed6a08
|
oid sha256:fe8a672f763bde80b901e4e8a0074658850942ed4f48e819b60bd67fef8bd025
|
||||||
size 199952
|
size 200840
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:67c73fd1862c07dc1d93b0e4b98d13703a01b54a268d6352fe7776d0c58376f4
|
oid sha256:050893bf116964c98d46d78d850cbcbfd92d11d612e5018c291f23d0ab4fffdd
|
||||||
size 146945
|
size 144601
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d3e372a52fe24ebede306c07107f482426b7ae2a8b8ef6c9e6a8a85af433b7a5
|
oid sha256:9651e8d3e5ccc6f2a0ea0da2b4054f901ef28de0e2175ecb457d12ac318be64a
|
||||||
size 197983
|
size 195662
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2bff344e5fbc9b9ccb88b22866c511cd51cc6970127955947a54b33dfdcaa5da
|
oid sha256:35a2456bd43b0c5f15392cf37bad123ac10ca4362bde64e6a096f77be597d67e
|
||||||
size 85667
|
size 84840
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a154db9b1f771604817d54cc804309eaf80b92ec44632225c11cc9118f2ee6cb
|
oid sha256:3d0d342544d63dd9df0dd3f94e2a40d3bca7d9b91227e8b8b4e883efef85f19a
|
||||||
size 85038
|
size 86265
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user