diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ce2bb7aaa..a9e8845e4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,4 +6,4 @@ * [ ] Pull request is based on the develop branch * [ ] Pull request updates [CHANGES.rst](https://github.com/vector-im/riot-ios/blob/develop/CHANGES.rst) * [ ] Pull request includes screenshots or videos of UI changes -* [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off) +* [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off) diff --git a/CHANGES.rst b/CHANGES.rst index 9d1be71c4..87e646278 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,27 @@ +Changes in 0.11.0 (2020-04-17) +=============================================== + +Improvements: + * Upgrade MatrixKit version ([v0.12.0](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.12.0)). + * Crypto: Enable E2EE by default for DM + * Crypto: Cross-signing support + * Crypto: Do not warn anymore for unknown devices. Trust on First Use. + * RoomVC: Update encryption decoration with shields (#2934, #2930, #2906). + * Settings: Remove "End-to-End Encryption" from the LABS section (#2941). + * Room decoration: Use shields instead of padlocks (#2906). + * Room decoration: Remove horizontal empty space when there is no decoration badge to set on room message (#2978). + * RoomVC: For a room preview use room canonical alias if present when joining a room. + * Update Matomo app id (#3001) + * Verification by DM: Support QR code (#2921). + * Cross-Signing: Detect and expose new sign-ins (#2918). + * Cross-signing: Complete security at the end of sign in process( #3003). + * Make decoration uniform (#2972). + * DeactivateAccountViewController: Respect active theme (PR #3107). + * Verification by emojis: Center emojis in screen horizontally (PR #3119). + +Bug fix: + * Key backup banner is not hidden correctly (#2899). + Changes in 0.10.5 (2020-04-01) =============================================== @@ -5,7 +29,7 @@ Bug fix: * Fix error when joining some public rooms, thanks to @chrismoos (PR #2888). * Fix crash due to malformed widget (#2997). * Push notifications: Avoid any automatic deactivation (vector-im/riot-ios#3017). - * Fix links breaking user out of SSO flow. + * Fix links breaking user out of SSO flow, thanks to @schultetwin (#3039). Changes in 0.10.4 (2019-12-11) =============================================== diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 50d20cbb4..117d3f6d2 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing code to Riot iOS ================================== -riot-ios follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst +riot-ios follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md diff --git a/Podfile b/Podfile index ee2ceb707..68c5cf206 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ use_frameworks! # Different flavours of pods to MatrixKit # The current MatrixKit pod version -$matrixKitVersion = '0.11.4' +$matrixKitVersion = '0.12.0' # The develop branch version #$matrixKitVersion = 'develop' @@ -61,8 +61,7 @@ abstract_target 'RiotPods' do pod 'GBDeviceInfo', '~> 6.3.0' pod 'Reusable', '~> 4.1' - pod 'SwiftUTI', :git => 'https://github.com/speramusinc/SwiftUTI.git', :branch => 'master' - + # Piwik for analytics pod 'MatomoTracker', '~> 7.2.0' @@ -77,7 +76,9 @@ abstract_target 'RiotPods' do target "Riot" do import_MatrixKit - pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' + pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' + pod 'KTCenterFlowLayout', '~> 1.3.1' + pod 'ZXingObjC', '~> 3.6.5' target 'RiotTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index 506e78114..5ec7bb55f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -44,46 +44,44 @@ PODS: - GZIP (1.2.3) - HPGrowingTextView (1.1) - JitsiMeetSDK (2.3.1) + - KTCenterFlowLayout (1.3.1) - libbase58 (0.1.4) - libPhoneNumber-iOS (0.9.15) - MatomoTracker (7.2.0): - MatomoTracker/Core (= 7.2.0) - MatomoTracker/Core (7.2.0) - - MatrixKit (0.11.4): + - MatrixKit (0.12.0): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.11.4) - - MatrixSDK (= 0.15.2) - - SwiftUTI (~> 1.0.6) - - MatrixKit/AppExtension (0.11.4): + - MatrixKit/Core (= 0.12.0) + - MatrixSDK (= 0.16.0) + - MatrixKit/AppExtension (0.12.0): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.15.2) - - SwiftUTI (~> 1.0.6) - - MatrixKit/Core (0.11.4): + - MatrixSDK (= 0.16.0) + - MatrixKit/Core (0.12.0): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.15.2) - - SwiftUTI (~> 1.0.6) - - MatrixSDK (0.15.2): - - MatrixSDK/Core (= 0.15.2) - - MatrixSDK/Core (0.15.2): + - MatrixSDK (= 0.16.0) + - MatrixSDK (0.16.0): + - MatrixSDK/Core (= 0.16.0) + - MatrixSDK/Core (0.16.0): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.2) - libbase58 (~> 0.1.4) - OLMKit (~> 3.1.0) - Realm (~> 3.17.3) - - MatrixSDK/JingleCallStack (0.15.2): + - MatrixSDK/JingleCallStack (0.16.0): - JitsiMeetSDK (~> 2.3.1) - MatrixSDK/Core - - MatrixSDK/SwiftSupport (0.15.2): + - MatrixSDK/SwiftSupport (0.16.0): - MatrixSDK/Core - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) @@ -100,24 +98,27 @@ PODS: - Reusable/View (4.1.0) - SwiftGen (6.1.0) - SwiftLint (0.36.0) - - SwiftUTI (1.0.7) - zxcvbn-ios (1.0.4) + - ZXingObjC (3.6.5): + - ZXingObjC/All (= 3.6.5) + - ZXingObjC/All (3.6.5) DEPENDENCIES: - cmark - DGCollectionViewLeftAlignFlowLayout (~> 1.0.4) - GBDeviceInfo (~> 6.3.0) + - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.2.0) - - MatrixKit (= 0.11.4) - - MatrixKit/AppExtension (= 0.11.4) + - MatrixKit (= 0.12.0) + - MatrixKit/AppExtension (= 0.12.0) - MatrixSDK/JingleCallStack - MatrixSDK/SwiftSupport - OLMKit - Reusable (~> 4.1) - SwiftGen (~> 6.1) - SwiftLint (~> 0.36.0) - - SwiftUTI (from `https://github.com/speramusinc/SwiftUTI.git`, branch `master`) - zxcvbn-ios + - ZXingObjC (~> 3.6.5) SPEC REPOS: trunk: @@ -130,6 +131,7 @@ SPEC REPOS: - GZIP - HPGrowingTextView - JitsiMeetSDK + - KTCenterFlowLayout - libbase58 - libPhoneNumber-iOS - MatomoTracker @@ -141,16 +143,7 @@ SPEC REPOS: - SwiftGen - SwiftLint - zxcvbn-ios - -EXTERNAL SOURCES: - SwiftUTI: - :branch: master - :git: https://github.com/speramusinc/SwiftUTI.git - -CHECKOUT OPTIONS: - SwiftUTI: - :commit: b6b46942fb3aad819610851f62a70e17a528444e - :git: https://github.com/speramusinc/SwiftUTI.git + - ZXingObjC SPEC CHECKSUMS: AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 @@ -162,19 +155,20 @@ SPEC CHECKSUMS: GZIP: af5c90ef903776a7e9afe6ebebd794a84a2929d4 HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 JitsiMeetSDK: 69e4978fbab21f9a535d1bec3b8d43721a4c72b2 + KTCenterFlowLayout: 6e02b50ab2bd865025ae82fe266ed13b6d9eaf97 libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 MatomoTracker: 6f89e2561083685a360e223fb663e9ccd57c1d1a - MatrixKit: 3db15f216e2de4d53b1405434028d44f42d4af22 - MatrixSDK: f83bd3c5519c1cb9ac3998f6423574cf528f0250 + MatrixKit: 2ae83c621caf3c11681e00a0b802086ac4d1106c + MatrixSDK: caad3d93c665163d9d414f6876e9a7d220c6c46f OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 Realm: 5a1d9d47bfc101dd597668b1a8af4288a2557f6d Reusable: 82be188f29d96dc5eff0db7b2393bcc08d2cdd5b SwiftGen: f872ca75cbd17bf7103c17f13dcfa0d9a15667b0 SwiftLint: fc9859e4e1752340664851f667bb1898b9c90114 - SwiftUTI: 917993c124f8eac25e88ced0202fc58d7eb50fa8 zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c + ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: c7b37d248a316ae786328d88148e3155fea6bee3 +PODFILE CHECKSUM: 6f647ad2f6c765719fd7e9a5d1e9e77e797d328f -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.1 diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index fd3d454aa..447eeb682 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -37,7 +37,7 @@ 3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB452256558300AD6A5C /* TemplateScreenViewState.swift */; }; 3232AB512256558300AD6A5C /* TemplateScreenViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB462256558300AD6A5C /* TemplateScreenViewAction.swift */; }; 3232AB522256558300AD6A5C /* TemplateScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB472256558300AD6A5C /* TemplateScreenViewModel.swift */; }; - 3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB95225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift */; }; + 3232ABA1225730E100AD6A5C /* KeyVerificationCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB95225730E100AD6A5C /* KeyVerificationCoordinatorType.swift */; }; 3232ABA2225730E100AD6A5C /* DeviceVerificationStartViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3232AB97225730E100AD6A5C /* DeviceVerificationStartViewController.storyboard */; }; 3232ABA3225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB98225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift */; }; 3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB99225730E100AD6A5C /* DeviceVerificationStartViewAction.swift */; }; @@ -46,17 +46,7 @@ 3232ABA7225730E100AD6A5C /* DeviceVerificationStartCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB9C225730E100AD6A5C /* DeviceVerificationStartCoordinator.swift */; }; 3232ABA8225730E100AD6A5C /* DeviceVerificationStartViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB9D225730E100AD6A5C /* DeviceVerificationStartViewState.swift */; }; 3232ABA9225730E100AD6A5C /* DeviceVerificationStartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB9E225730E100AD6A5C /* DeviceVerificationStartViewModel.swift */; }; - 3232ABAA225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AB9F225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift */; }; - 3232ABAB225730E100AD6A5C /* DeviceVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABA0225730E100AD6A5C /* DeviceVerificationCoordinator.swift */; }; - 3232ABB52257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABAD2257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift */; }; - 3232ABB62257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3232ABAE2257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard */; }; - 3232ABB72257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABAF2257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift */; }; - 3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABB02257BE6400AD6A5C /* DeviceVerificationVerifyCoordinator.swift */; }; - 3232ABB92257BE6500AD6A5C /* DeviceVerificationVerifyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABB12257BE6400AD6A5C /* DeviceVerificationVerifyViewController.swift */; }; - 3232ABBA2257BE6500AD6A5C /* DeviceVerificationVerifyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABB22257BE6400AD6A5C /* DeviceVerificationVerifyViewModel.swift */; }; - 3232ABBB2257BE6500AD6A5C /* DeviceVerificationVerifyViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABB32257BE6400AD6A5C /* DeviceVerificationVerifyViewState.swift */; }; - 3232ABBC2257BE6500AD6A5C /* DeviceVerificationVerifyViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABB42257BE6400AD6A5C /* DeviceVerificationVerifyViewAction.swift */; }; - 3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABBF22594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift */; }; + 3232ABAB225730E100AD6A5C /* KeyVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABA0225730E100AD6A5C /* KeyVerificationCoordinator.swift */; }; 3232ABC2225B996200AD6A5C /* Themable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ABC1225B996100AD6A5C /* Themable.swift */; }; 323AB947232BD74600C1451F /* AuthFallBackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 323AB946232BD74600C1451F /* AuthFallBackViewController.m */; }; 324A204F225FC571004FE8B0 /* DeviceVerificationIncomingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324A2047225FC571004FE8B0 /* DeviceVerificationIncomingViewController.storyboard */; }; @@ -67,6 +57,14 @@ 324A2054225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324A204C225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift */; }; 324A2055225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324A204D225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift */; }; 324A2056225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324A204E225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift */; }; + 32607D6C243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D64243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift */; }; + 32607D6D243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D65243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift */; }; + 32607D6E243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D66243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift */; }; + 32607D6F243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32607D67243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard */; }; + 32607D70243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D68243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift */; }; + 32607D71243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D69243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift */; }; + 32607D72243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D6A243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift */; }; + 32607D73243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D6B243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift */; }; 3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3275FD8B21A5A2C500B9C13D /* TermsView.swift */; }; 3281BCF72201FA4200F4A383 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3281BCF62201FA4200F4A383 /* UIControl.swift */; }; 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */ = {isa = PBXBuildFile; fileRef = 3284A35020A07C210044F922 /* postMessageAPI.js */; }; @@ -74,10 +72,10 @@ 32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */; }; 32891D6B2264CBA300C82226 /* SimpleScreenTemplateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D692264CBA300C82226 /* SimpleScreenTemplateViewController.swift */; }; 32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D6A2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard */; }; - 32891D702264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */; }; - 32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */; }; - 32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */; }; - 32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */; }; + 32891D75226728EE00C82226 /* KeyVerificationDataLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D73226728EE00C82226 /* KeyVerificationDataLoadingViewController.swift */; }; + 32891D76226728EF00C82226 /* KeyVerificationDataLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D74226728EE00C82226 /* KeyVerificationDataLoadingViewController.storyboard */; }; + 3291DC8A23E0BE820009732F /* Security.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3291DC8923E0BE820009732F /* Security.storyboard */; }; + 3291DC8D23E0BFF10009732F /* SecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3291DC8C23E0BFF10009732F /* SecurityViewController.m */; }; 329E746622CD02EA006F9797 /* BubbleReactionActionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */; }; 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */; }; 32A6001622C661100042C1D9 /* EditHistoryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */; }; @@ -98,6 +96,8 @@ 32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */; }; 32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995421FA2AB700698084 /* SettingsKeyBackupViewAction.swift */; }; 32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995621FB07A400698084 /* SettingsKeyBackupTableViewSection.swift */; }; + 32D5D16023E1EE2700E3E37C /* ManageSessionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */; }; + 32D5D16123E1EE2700E3E37C /* ManageSession.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */; }; 32DB557522FDADE50016329E /* ServiceTermsModalCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556922FDADE50016329E /* ServiceTermsModalCoordinatorType.swift */; }; 32DB557622FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556A22FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift */; }; 32DB557722FDADE50016329E /* ServiceTermsModalCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556B22FDADE50016329E /* ServiceTermsModalCoordinator.swift */; }; @@ -109,12 +109,12 @@ 32DB557D22FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557222FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift */; }; 32DB557E22FDADE50016329E /* ServiceTermsModalScreenViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557322FDADE50016329E /* ServiceTermsModalScreenViewAction.swift */; }; 32DB557F22FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557422FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift */; }; - 32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9632270623000BBA352 /* DeviceVerificationDataLoadingCoordinator.swift */; }; - 32F6B96A2270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9642270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift */; }; - 32F6B96B2270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9652270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift */; }; - 32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9662270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift */; }; - 32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9672270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift */; }; - 32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9682270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift */; }; + 32F6B9692270623100BBA352 /* KeyVerificationDataLoadingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9632270623000BBA352 /* KeyVerificationDataLoadingCoordinator.swift */; }; + 32F6B96A2270623100BBA352 /* KeyVerificationDataLoadingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9642270623100BBA352 /* KeyVerificationDataLoadingViewState.swift */; }; + 32F6B96B2270623100BBA352 /* KeyVerificationDataLoadingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9652270623100BBA352 /* KeyVerificationDataLoadingViewAction.swift */; }; + 32F6B96C2270623100BBA352 /* KeyVerificationDataLoadingCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9662270623100BBA352 /* KeyVerificationDataLoadingCoordinatorType.swift */; }; + 32F6B96D2270623100BBA352 /* KeyVerificationDataLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9672270623100BBA352 /* KeyVerificationDataLoadingViewModel.swift */; }; + 32F6B96E2270623100BBA352 /* KeyVerificationDataLoadingViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9682270623100BBA352 /* KeyVerificationDataLoadingViewModelType.swift */; }; 32FDC1CD2386CD390084717A /* RiotSettingIntegrationProvisioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32FDC1CC2386CD390084717A /* RiotSettingIntegrationProvisioning.swift */; }; 3AF393339D2D566CE14AC200 /* Pods_RiotTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */; }; 405FD41D306133A48D9B5AA1 /* Pods_RiotPods_Riot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1ACF09217ADF1D7E7A35BC02 /* Pods_RiotPods_Riot.framework */; }; @@ -129,6 +129,12 @@ B105778B221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */; }; B105778D2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B105778C2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift */; }; B105778F2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B105778E2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard */; }; + B108931F23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */; }; + B108932123AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */; }; + B108932323AB908A00802670 /* KeyVerificationRequestStatusViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */; }; + B108932523AB93A200802670 /* KeyVerificationConclusionViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */; }; + B108932823ABEE6800802670 /* BubbleCellReadReceiptsDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */; }; + B108932A23ACBA0B00802670 /* SizingViewHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932923ACBA0B00802670 /* SizingViewHeight.swift */; }; B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098BDA21ECE09E000DDA48 /* Strings.swift */; }; B1098BE121ECE09F000DDA48 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098BDC21ECE09E000DDA48 /* Images.swift */; }; B1098BE321ECE09F000DDA48 /* RiotDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098BDE21ECE09E000DDA48 /* RiotDefaults.swift */; }; @@ -169,12 +175,25 @@ B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */; }; B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */; }; B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */; }; + B12D79FB23E2462200FACEDC /* UserVerificationStartCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */; }; + B12D79FC23E2462200FACEDC /* UserVerificationStartViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */; }; + B12D79FD23E2462200FACEDC /* UserVerificationStartViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */; }; + B12D79FE23E2462200FACEDC /* UserVerificationStartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */; }; + B12D79FF23E2462200FACEDC /* UserVerificationStartViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */; }; + B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */; }; + B12D7A0123E2462200FACEDC /* UserVerificationStartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */; }; + B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */; }; B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */; }; B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */; }; B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */; }; B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C22021FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift */; }; B139C22321FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C22221FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift */; }; B139C22521FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C22421FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift */; }; + B14084C623BF76890010F692 /* BubbleCellContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084C523BF76890010F692 /* BubbleCellContentView.swift */; }; + B14084C823BF76CB0010F692 /* BubbleCellContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */; }; + B14084CA23BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */; }; + B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */; }; + B14084CE23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */; }; B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A121F87F7100E3F5FE /* OperationQueue.swift */; }; B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */; }; B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */; }; @@ -190,6 +209,17 @@ B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142D22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift */; }; B152C73122DF561E0041315A /* EmojiServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B152C73022DF561E0041315A /* EmojiServiceTests.swift */; }; B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = B152C73422DF599B0041315A /* apple_emojis_data.json */; }; + B1550FCA2420E8F500CE097B /* QRCodeReaderViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1550FC72420E8F400CE097B /* QRCodeReaderViewController.storyboard */; }; + B1550FCB2420E8F500CE097B /* QRCodeReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1550FC82420E8F400CE097B /* QRCodeReaderViewController.swift */; }; + B1550FCC2420E8F500CE097B /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1550FC92420E8F400CE097B /* QRCodeGenerator.swift */; }; + B157A7B42445BD86008A5504 /* KeyVerificationScanConfirmationViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7AC2445BD85008A5504 /* KeyVerificationScanConfirmationViewState.swift */; }; + B157A7B52445BD86008A5504 /* KeyVerificationScanConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7AD2445BD85008A5504 /* KeyVerificationScanConfirmationViewController.swift */; }; + B157A7B62445BD86008A5504 /* KeyVerificationScanConfirmationViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7AE2445BD85008A5504 /* KeyVerificationScanConfirmationViewAction.swift */; }; + B157A7B72445BD86008A5504 /* KeyVerificationScanConfirmationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7AF2445BD85008A5504 /* KeyVerificationScanConfirmationViewModel.swift */; }; + B157A7B82445BD86008A5504 /* KeyVerificationScanConfirmationViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7B02445BD85008A5504 /* KeyVerificationScanConfirmationViewModelType.swift */; }; + B157A7B92445BD86008A5504 /* KeyVerificationScanConfirmationViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B157A7B12445BD85008A5504 /* KeyVerificationScanConfirmationViewController.storyboard */; }; + B157A7BA2445BD86008A5504 /* KeyVerificationScanConfirmationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7B22445BD85008A5504 /* KeyVerificationScanConfirmationCoordinator.swift */; }; + B157A7BB2445BD86008A5504 /* KeyVerificationScanConfirmationCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157A7B32445BD85008A5504 /* KeyVerificationScanConfirmationCoordinatorType.swift */; }; B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */; }; B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */; }; B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */; }; @@ -240,6 +270,18 @@ B169331720F3CBE000746532 /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = B16932F920F3C51900746532 /* RecentCellData.m */; }; B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */; }; B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1798301211B13B3001FD722 /* OnBoardingManager.swift */; }; + B183226623F55D6B0035B2E8 /* CameraAccessManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B183226523F55D6B0035B2E8 /* CameraAccessManager.swift */; }; + B183226823F561380035B2E8 /* CameraAccessAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B183226723F561380035B2E8 /* CameraAccessAlertPresenter.swift */; }; + B183226C23F59F810035B2E8 /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B183226B23F59F810035B2E8 /* CloseButton.swift */; }; + B18DEDD4243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDCC243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewModelType.swift */; }; + B18DEDD5243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDCD243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewModel.swift */; }; + B18DEDD6243377C10075FEF7 /* KeyVerificationSelfVerifyWaitCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDCE243377C00075FEF7 /* KeyVerificationSelfVerifyWaitCoordinator.swift */; }; + B18DEDD7243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B18DEDCF243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewController.storyboard */; }; + B18DEDD8243377C10075FEF7 /* KeyVerificationSelfVerifyWaitCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDD0243377C00075FEF7 /* KeyVerificationSelfVerifyWaitCoordinatorType.swift */; }; + B18DEDD9243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDD1243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.swift */; }; + B18DEDDA243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDD2243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewState.swift */; }; + B18DEDDB243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDD3243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewAction.swift */; }; + B18DEDDD2433967C0075FEF7 /* KeyVerificationFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18DEDDC2433967C0075FEF7 /* KeyVerificationFlow.swift */; }; B190F55922CE356800AEB493 /* EditHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */; }; B190F55B22CE35FD00AEB493 /* EditHistoryHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */; }; B190F55D22CE5A9700AEB493 /* EditHistorySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */; }; @@ -251,6 +293,7 @@ B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */; }; B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */; }; B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */; }; + B197B7C6243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */; }; B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */; }; B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */; }; B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A5B33D227ADF2A004CBA85 /* UIImage.swift */; }; @@ -262,6 +305,14 @@ B1A6C111238BD236002882FD /* SlidingModalContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A6C110238BD236002882FD /* SlidingModalContainerView.swift */; }; B1A6C113238BD245002882FD /* SlidingModalContainerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1A6C112238BD245002882FD /* SlidingModalContainerView.xib */; }; B1B12B2922942315002CB419 /* UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B12B2822942315002CB419 /* UITouch.swift */; }; + B1B336BE242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336B6242B933300F95EC4 /* KeyVerificationSelfVerifyStartViewState.swift */; }; + B1B336BF242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336B7242B933300F95EC4 /* KeyVerificationSelfVerifyStartViewController.swift */; }; + B1B336C0242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336B8242B933400F95EC4 /* KeyVerificationSelfVerifyStartViewModelType.swift */; }; + B1B336C1242B933700F95EC4 /* KeyVerificationSelfVerifyStartCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336B9242B933500F95EC4 /* KeyVerificationSelfVerifyStartCoordinatorType.swift */; }; + B1B336C2242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1B336BA242B933500F95EC4 /* KeyVerificationSelfVerifyStartViewController.storyboard */; }; + B1B336C3242B933700F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336BB242B933500F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift */; }; + B1B336C4242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336BC242B933600F95EC4 /* KeyVerificationSelfVerifyStartViewAction.swift */; }; + B1B336C5242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B336BD242B933600F95EC4 /* KeyVerificationSelfVerifyStartViewModel.swift */; }; B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */; }; B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */; }; B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567E20EE6C4C00210D55 /* SettingsViewController.m */; }; @@ -517,6 +568,27 @@ B1BD71BC238E8F9600BA92E2 /* WidgetPermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */; }; B1BD71BF238EA56700BA92E2 /* WidgetPermissionViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */; }; B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */; }; + B1BEE71423DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */; }; + B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */; }; + B1BEE71623DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */; }; + B1BEE72A23DF38B20003A4CB /* UserVerificationSessionStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */; }; + B1BEE72B23DF38B20003A4CB /* UserVerificationSessionStatusCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */; }; + B1BEE73423DF44A60003A4CB /* UserVerificationSessionsStatusViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */; }; + B1BEE73523DF44A60003A4CB /* UserVerificationSessionsStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */; }; + B1BEE73623DF44A60003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */; }; + B1BEE73723DF44A60003A4CB /* UserVerificationSessionsStatusViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */; }; + B1BEE73823DF44A60003A4CB /* UserVerificationSessionsStatusViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */; }; + B1BEE73923DF44A60003A4CB /* UserVerificationSessionsStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */; }; + B1BEE73A23DF44A60003A4CB /* UserVerificationSessionsStatusViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */; }; + B1BEE73B23DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */; }; + B1BEE74623E093260003A4CB /* UserVerificationSessionStatusViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */; }; + B1BEE74723E093260003A4CB /* UserVerificationSessionStatusViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */; }; + B1BEE74823E093260003A4CB /* UserVerificationSessionStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */; }; + B1BEE74923E093260003A4CB /* UserVerificationSessionStatusViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */; }; + B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */; }; + B1BEE74B23E093260003A4CB /* UserVerificationSessionStatusViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */; }; + B1BEE74C23E093260003A4CB /* UserVerificationSessionStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */; }; + B1BEE74D23E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */; }; B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */; }; B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */; }; B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */; }; @@ -531,6 +603,11 @@ B1C45A8A232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C45A81232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift */; }; B1C45A8B232A8C2600165425 /* SettingsIdentityServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C45A82232A8C2600165425 /* SettingsIdentityServerViewModel.swift */; }; B1C45A8C232A8C2600165425 /* SettingsIdentityServerViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C45A83232A8C2600165425 /* SettingsIdentityServerViewAction.swift */; }; + B1C543A4239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */; }; + B1C543A6239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */; }; + B1C543AE23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */; }; + B1C543B023A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */; }; + B1C543B223A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */; }; B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; }; B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; }; B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; }; @@ -544,6 +621,28 @@ B1C562E9228C7CF20037F12A /* ContextualMenuItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1C562E7228C7CF20037F12A /* ContextualMenuItemView.xib */; }; B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2621EF6913000D1D89 /* UIViewController.swift */; }; B1CA3A2921EF692B000D1D89 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2821EF692B000D1D89 /* UIView.swift */; }; + B1CE83B62422812100D07506 /* KeyVerificationCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83B52422812000D07506 /* KeyVerificationCoordinatorBridgePresenter.swift */; }; + B1CE83B92422815C00D07506 /* KeyVerificationKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83B72422815900D07506 /* KeyVerificationKind.swift */; }; + B1CE83BA2422815C00D07506 /* KeyVerificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83B82422815A00D07506 /* KeyVerificationService.swift */; }; + B1CE83D32422817200D07506 /* KeyVerificationVerifiedViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1CE83BC2422817000D07506 /* KeyVerificationVerifiedViewController.storyboard */; }; + B1CE83D42422817200D07506 /* KeyVerificationVerifiedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83BD2422817000D07506 /* KeyVerificationVerifiedViewController.swift */; }; + B1CE83D52422817200D07506 /* KeyVerificationVerifyByScanningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C02422817100D07506 /* KeyVerificationVerifyByScanningViewController.swift */; }; + B1CE83D62422817200D07506 /* KeyVerificationVerifyByScanningCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C12422817100D07506 /* KeyVerificationVerifyByScanningCoordinator.swift */; }; + B1CE83D72422817200D07506 /* KeyVerificationVerifyByScanningViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C22422817100D07506 /* KeyVerificationVerifyByScanningViewModelType.swift */; }; + B1CE83D82422817200D07506 /* KeyVerificationVerifyByScanningCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C32422817100D07506 /* KeyVerificationVerifyByScanningCoordinatorType.swift */; }; + B1CE83D92422817200D07506 /* KeyVerificationVerifyByScanningViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C42422817100D07506 /* KeyVerificationVerifyByScanningViewAction.swift */; }; + B1CE83DA2422817200D07506 /* KeyVerificationVerifyByScanningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C52422817100D07506 /* KeyVerificationVerifyByScanningViewModel.swift */; }; + B1CE83DB2422817200D07506 /* KeyVerificationVerifyByScanningViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1CE83C62422817100D07506 /* KeyVerificationVerifyByScanningViewController.storyboard */; }; + B1CE83DC2422817200D07506 /* KeyVerificationVerifyByScanningViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C72422817100D07506 /* KeyVerificationVerifyByScanningViewState.swift */; }; + B1CE83DD2422817200D07506 /* KeyVerificationVerifyBySASViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83C92422817100D07506 /* KeyVerificationVerifyBySASViewAction.swift */; }; + B1CE83DE2422817200D07506 /* KeyVerificationVerifyBySASViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83CA2422817100D07506 /* KeyVerificationVerifyBySASViewModelType.swift */; }; + B1CE83DF2422817200D07506 /* KeyVerificationVerifyBySASCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83CB2422817100D07506 /* KeyVerificationVerifyBySASCoordinatorType.swift */; }; + B1CE83E02422817200D07506 /* KeyVerificationVerifyBySASViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83CC2422817100D07506 /* KeyVerificationVerifyBySASViewController.swift */; }; + B1CE83E12422817200D07506 /* KeyVerificationVerifyBySASCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83CD2422817100D07506 /* KeyVerificationVerifyBySASCoordinator.swift */; }; + B1CE83E22422817200D07506 /* KeyVerificationVerifyBySASViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83CE2422817100D07506 /* KeyVerificationVerifyBySASViewState.swift */; }; + B1CE83E32422817200D07506 /* VerifyEmojiCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83D02422817100D07506 /* VerifyEmojiCollectionViewCell.swift */; }; + B1CE83E42422817200D07506 /* KeyVerificationVerifyBySASViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1CE83D12422817100D07506 /* KeyVerificationVerifyBySASViewController.storyboard */; }; + B1CE83E52422817200D07506 /* KeyVerificationVerifyBySASViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE83D22422817100D07506 /* KeyVerificationVerifyBySASViewModel.swift */; }; B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */; }; B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */; }; B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */; }; @@ -553,6 +652,7 @@ B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */; }; B1D211E822C195B400D939BD /* ReactionMenuItemViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */; }; B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */; }; + B1D44999243CD73700959523 /* RoomPowerLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D44998243CD73700959523 /* RoomPowerLevel.swift */; }; B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752521EE4E620067973F /* KeyboardAvoider.swift */; }; B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752621EE4E620067973F /* KeyboardNotification.swift */; }; B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752921EE52B10067973F /* KeyBackupSetupIntroViewController.swift */; }; @@ -681,7 +781,7 @@ 3232AB452256558300AD6A5C /* TemplateScreenViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewState.swift; sourceTree = ""; }; 3232AB462256558300AD6A5C /* TemplateScreenViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewAction.swift; sourceTree = ""; }; 3232AB472256558300AD6A5C /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = ""; }; - 3232AB95225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationCoordinatorType.swift; sourceTree = ""; }; + 3232AB95225730E100AD6A5C /* KeyVerificationCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationCoordinatorType.swift; sourceTree = ""; }; 3232AB97225730E100AD6A5C /* DeviceVerificationStartViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationStartViewController.storyboard; sourceTree = ""; }; 3232AB98225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationStartCoordinatorType.swift; sourceTree = ""; }; 3232AB99225730E100AD6A5C /* DeviceVerificationStartViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationStartViewAction.swift; sourceTree = ""; }; @@ -690,17 +790,7 @@ 3232AB9C225730E100AD6A5C /* DeviceVerificationStartCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationStartCoordinator.swift; sourceTree = ""; }; 3232AB9D225730E100AD6A5C /* DeviceVerificationStartViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationStartViewState.swift; sourceTree = ""; }; 3232AB9E225730E100AD6A5C /* DeviceVerificationStartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationStartViewModel.swift; sourceTree = ""; }; - 3232AB9F225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationCoordinatorBridgePresenter.swift; sourceTree = ""; }; - 3232ABA0225730E100AD6A5C /* DeviceVerificationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationCoordinator.swift; sourceTree = ""; }; - 3232ABAD2257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyCoordinatorType.swift; sourceTree = ""; }; - 3232ABAE2257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationVerifyViewController.storyboard; sourceTree = ""; }; - 3232ABAF2257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyViewModelType.swift; sourceTree = ""; }; - 3232ABB02257BE6400AD6A5C /* DeviceVerificationVerifyCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyCoordinator.swift; sourceTree = ""; }; - 3232ABB12257BE6400AD6A5C /* DeviceVerificationVerifyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyViewController.swift; sourceTree = ""; }; - 3232ABB22257BE6400AD6A5C /* DeviceVerificationVerifyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyViewModel.swift; sourceTree = ""; }; - 3232ABB32257BE6400AD6A5C /* DeviceVerificationVerifyViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyViewState.swift; sourceTree = ""; }; - 3232ABB42257BE6400AD6A5C /* DeviceVerificationVerifyViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifyViewAction.swift; sourceTree = ""; }; - 3232ABBF22594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyEmojiCollectionViewCell.swift; sourceTree = ""; }; + 3232ABA0225730E100AD6A5C /* KeyVerificationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationCoordinator.swift; sourceTree = ""; }; 3232ABC1225B996100AD6A5C /* Themable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themable.swift; sourceTree = ""; }; 323AB945232BD74600C1451F /* AuthFallBackViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AuthFallBackViewController.h; sourceTree = ""; }; 323AB946232BD74600C1451F /* AuthFallBackViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AuthFallBackViewController.m; sourceTree = ""; }; @@ -715,6 +805,14 @@ 325789A5237AB241009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/InfoPlist.strings; sourceTree = ""; }; 325789A6237AB27F009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Localizable.strings; sourceTree = ""; }; 325789A7237AB297009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Vector.strings; sourceTree = ""; }; + 32607D64243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewController.swift; sourceTree = ""; }; + 32607D65243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyCoordinatorType.swift; sourceTree = ""; }; + 32607D66243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewModelType.swift; sourceTree = ""; }; + 32607D67243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromPrivateKeyViewController.storyboard; sourceTree = ""; }; + 32607D68243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewModel.swift; sourceTree = ""; }; + 32607D69243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewState.swift; sourceTree = ""; }; + 32607D6A243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewAction.swift; sourceTree = ""; }; + 32607D6B243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyCoordinator.swift; sourceTree = ""; }; 3267EFB320E379FD00FF1CAA /* CHANGES.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES.rst; sourceTree = ""; }; 3267EFB420E379FD00FF1CAA /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AUTHORS.rst; sourceTree = ""; }; @@ -726,10 +824,11 @@ 32863A5B2384074C00D07C4A /* RiotSettingAllowedWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiotSettingAllowedWidgets.swift; sourceTree = ""; }; 32891D692264CBA300C82226 /* SimpleScreenTemplateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleScreenTemplateViewController.swift; sourceTree = ""; }; 32891D6A2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SimpleScreenTemplateViewController.storyboard; sourceTree = ""; }; - 32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationVerifiedViewController.storyboard; sourceTree = ""; }; - 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifiedViewController.swift; sourceTree = ""; }; - 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewController.swift; sourceTree = ""; }; - 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationDataLoadingViewController.storyboard; sourceTree = ""; }; + 32891D73226728EE00C82226 /* KeyVerificationDataLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingViewController.swift; sourceTree = ""; }; + 32891D74226728EE00C82226 /* KeyVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationDataLoadingViewController.storyboard; sourceTree = ""; }; + 3291DC8923E0BE820009732F /* Security.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Security.storyboard; sourceTree = ""; }; + 3291DC8B23E0BFF10009732F /* SecurityViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecurityViewController.h; sourceTree = ""; }; + 3291DC8C23E0BFF10009732F /* SecurityViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecurityViewController.m; sourceTree = ""; }; 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionActionViewCell.xib; sourceTree = ""; }; 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionActionViewCell.swift; sourceTree = ""; }; 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewState.swift; sourceTree = ""; }; @@ -756,6 +855,9 @@ 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupViewState.swift; sourceTree = ""; }; 32BF995421FA2AB700698084 /* SettingsKeyBackupViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupViewAction.swift; sourceTree = ""; }; 32BF995621FB07A400698084 /* SettingsKeyBackupTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupTableViewSection.swift; sourceTree = ""; }; + 32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ManageSessionViewController.m; sourceTree = ""; }; + 32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ManageSession.storyboard; sourceTree = ""; }; + 32D5D15F23E1EE2700E3E37C /* ManageSessionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ManageSessionViewController.h; sourceTree = ""; }; 32D7159E2146CC6F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Vector.strings; sourceTree = ""; }; 32D7159F2146CC7F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 32D715A02146CC8800DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -773,12 +875,12 @@ 32DB557222FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenCoordinatorType.swift; sourceTree = ""; }; 32DB557322FDADE50016329E /* ServiceTermsModalScreenViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenViewAction.swift; sourceTree = ""; }; 32DB557422FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenCoordinator.swift; sourceTree = ""; }; - 32F6B9632270623000BBA352 /* DeviceVerificationDataLoadingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingCoordinator.swift; sourceTree = ""; }; - 32F6B9642270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewState.swift; sourceTree = ""; }; - 32F6B9652270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewAction.swift; sourceTree = ""; }; - 32F6B9662270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingCoordinatorType.swift; sourceTree = ""; }; - 32F6B9672270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewModel.swift; sourceTree = ""; }; - 32F6B9682270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewModelType.swift; sourceTree = ""; }; + 32F6B9632270623000BBA352 /* KeyVerificationDataLoadingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingCoordinator.swift; sourceTree = ""; }; + 32F6B9642270623100BBA352 /* KeyVerificationDataLoadingViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingViewState.swift; sourceTree = ""; }; + 32F6B9652270623100BBA352 /* KeyVerificationDataLoadingViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingViewAction.swift; sourceTree = ""; }; + 32F6B9662270623100BBA352 /* KeyVerificationDataLoadingCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingCoordinatorType.swift; sourceTree = ""; }; + 32F6B9672270623100BBA352 /* KeyVerificationDataLoadingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingViewModel.swift; sourceTree = ""; }; + 32F6B9682270623100BBA352 /* KeyVerificationDataLoadingViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationDataLoadingViewModelType.swift; sourceTree = ""; }; 32FDC1CC2386CD390084717A /* RiotSettingIntegrationProvisioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiotSettingIntegrationProvisioning.swift; sourceTree = ""; }; 3942DD65EBEB7AE647C6392A /* Pods-RiotPods-SiriIntents.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-SiriIntents.debug.xcconfig"; path = "Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents.debug.xcconfig"; sourceTree = ""; }; 3D78489021AC9E6400B98A7D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -806,6 +908,12 @@ B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromPassphraseViewController.storyboard; sourceTree = ""; }; B105778C2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupSuccessFromRecoveryKeyViewController.swift; sourceTree = ""; }; B105778E2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard; sourceTree = ""; }; + B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalBubbleCell.swift; sourceTree = ""; }; + B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalViewData.swift; sourceTree = ""; }; + B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusViewData.swift; sourceTree = ""; }; + B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionViewData.swift; sourceTree = ""; }; + B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleCellReadReceiptsDisplayable.swift; sourceTree = ""; }; + B108932923ACBA0B00802670 /* SizingViewHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizingViewHeight.swift; sourceTree = ""; }; B1098BDA21ECE09E000DDA48 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; B1098BDC21ECE09E000DDA48 /* Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; }; B1098BDE21ECE09E000DDA48 /* RiotDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RiotDefaults.swift; sourceTree = ""; }; @@ -846,12 +954,25 @@ B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewAction.swift; sourceTree = ""; }; B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewState.swift; sourceTree = ""; }; B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageURLParser.swift; sourceTree = ""; }; + B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinator.swift; sourceTree = ""; }; + B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationStartViewController.storyboard; sourceTree = ""; }; + B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModelType.swift; sourceTree = ""; }; + B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewController.swift; sourceTree = ""; }; + B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewState.swift; sourceTree = ""; }; + B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinatorType.swift; sourceTree = ""; }; + B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModel.swift; sourceTree = ""; }; + B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewAction.swift; sourceTree = ""; }; B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = ""; }; B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = ""; }; B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = ""; }; B139C22021FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewState.swift; sourceTree = ""; }; B139C22221FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseCoordinatorType.swift; sourceTree = ""; }; B139C22421FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseCoordinator.swift; sourceTree = ""; }; + B14084C523BF76890010F692 /* BubbleCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleCellContentView.swift; sourceTree = ""; }; + B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleCellContentView.xib; sourceTree = ""; }; + B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift; sourceTree = ""; }; + B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionWithPaginationTitleBubbleCell.swift; sourceTree = ""; }; + B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift; sourceTree = ""; }; B140B4A121F87F7100E3F5FE /* OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupCoordinatorBridgePresenter.swift; sourceTree = ""; }; B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorBridgePresenter.swift; sourceTree = ""; }; @@ -868,6 +989,17 @@ B152C72D22DF55D80041315A /* RiotTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RiotTests-Bridging-Header.h"; sourceTree = ""; }; B152C73022DF561E0041315A /* EmojiServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceTests.swift; sourceTree = ""; }; B152C73422DF599B0041315A /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = ""; }; + B1550FC72420E8F400CE097B /* QRCodeReaderViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = QRCodeReaderViewController.storyboard; sourceTree = ""; }; + B1550FC82420E8F400CE097B /* QRCodeReaderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReaderViewController.swift; sourceTree = ""; }; + B1550FC92420E8F400CE097B /* QRCodeGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = ""; }; + B157A7AC2445BD85008A5504 /* KeyVerificationScanConfirmationViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewState.swift; sourceTree = ""; }; + B157A7AD2445BD85008A5504 /* KeyVerificationScanConfirmationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewController.swift; sourceTree = ""; }; + B157A7AE2445BD85008A5504 /* KeyVerificationScanConfirmationViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewAction.swift; sourceTree = ""; }; + B157A7AF2445BD85008A5504 /* KeyVerificationScanConfirmationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewModel.swift; sourceTree = ""; }; + B157A7B02445BD85008A5504 /* KeyVerificationScanConfirmationViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationViewModelType.swift; sourceTree = ""; }; + B157A7B12445BD85008A5504 /* KeyVerificationScanConfirmationViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationScanConfirmationViewController.storyboard; sourceTree = ""; }; + B157A7B22445BD85008A5504 /* KeyVerificationScanConfirmationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationCoordinator.swift; sourceTree = ""; }; + B157A7B32445BD85008A5504 /* KeyVerificationScanConfirmationCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationScanConfirmationCoordinatorType.swift; sourceTree = ""; }; B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinator.swift; sourceTree = ""; }; B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewModelType.swift; sourceTree = ""; }; B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinatorType.swift; sourceTree = ""; }; @@ -966,6 +1098,18 @@ B169331320F3CAFC00746532 /* PublicRoomsDirectoryDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomsDirectoryDataSource.h; sourceTree = ""; }; B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GDPRConsentViewController.swift; sourceTree = ""; }; B1798301211B13B3001FD722 /* OnBoardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingManager.swift; sourceTree = ""; }; + B183226523F55D6B0035B2E8 /* CameraAccessManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraAccessManager.swift; sourceTree = ""; }; + B183226723F561380035B2E8 /* CameraAccessAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraAccessAlertPresenter.swift; sourceTree = ""; }; + B183226B23F59F810035B2E8 /* CloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseButton.swift; sourceTree = ""; }; + B18DEDCC243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitViewModelType.swift; sourceTree = ""; }; + B18DEDCD243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitViewModel.swift; sourceTree = ""; }; + B18DEDCE243377C00075FEF7 /* KeyVerificationSelfVerifyWaitCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitCoordinator.swift; sourceTree = ""; }; + B18DEDCF243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationSelfVerifyWaitViewController.storyboard; sourceTree = ""; }; + B18DEDD0243377C00075FEF7 /* KeyVerificationSelfVerifyWaitCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitCoordinatorType.swift; sourceTree = ""; }; + B18DEDD1243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitViewController.swift; sourceTree = ""; }; + B18DEDD2243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitViewState.swift; sourceTree = ""; }; + B18DEDD3243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyWaitViewAction.swift; sourceTree = ""; }; + B18DEDDC2433967C0075FEF7 /* KeyVerificationFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationFlow.swift; sourceTree = ""; }; B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistoryHeaderView.swift; sourceTree = ""; }; B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditHistoryHeaderView.xib; sourceTree = ""; }; B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistorySection.swift; sourceTree = ""; }; @@ -977,6 +1121,7 @@ B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionsView.xib; sourceTree = ""; }; B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModelType.swift; sourceTree = ""; }; B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutosizedCollectionView.swift; sourceTree = ""; }; + B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTrustLevelBadgeImageHelper.swift; sourceTree = ""; }; B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorType.swift; sourceTree = ""; }; B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinator.swift; sourceTree = ""; }; B1A5B33D227ADF2A004CBA85 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; @@ -988,6 +1133,14 @@ B1A6C110238BD236002882FD /* SlidingModalContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingModalContainerView.swift; sourceTree = ""; }; B1A6C112238BD245002882FD /* SlidingModalContainerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SlidingModalContainerView.xib; sourceTree = ""; }; B1B12B2822942315002CB419 /* UITouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouch.swift; sourceTree = ""; }; + B1B336B6242B933300F95EC4 /* KeyVerificationSelfVerifyStartViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartViewState.swift; sourceTree = ""; }; + B1B336B7242B933300F95EC4 /* KeyVerificationSelfVerifyStartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartViewController.swift; sourceTree = ""; }; + B1B336B8242B933400F95EC4 /* KeyVerificationSelfVerifyStartViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartViewModelType.swift; sourceTree = ""; }; + B1B336B9242B933500F95EC4 /* KeyVerificationSelfVerifyStartCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartCoordinatorType.swift; sourceTree = ""; }; + B1B336BA242B933500F95EC4 /* KeyVerificationSelfVerifyStartViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationSelfVerifyStartViewController.storyboard; sourceTree = ""; }; + B1B336BB242B933500F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartCoordinator.swift; sourceTree = ""; }; + B1B336BC242B933600F95EC4 /* KeyVerificationSelfVerifyStartViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartViewAction.swift; sourceTree = ""; }; + B1B336BD242B933600F95EC4 /* KeyVerificationSelfVerifyStartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationSelfVerifyStartViewModel.swift; sourceTree = ""; }; B1B5567920EE6C4C00210D55 /* CountryPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryPickerViewController.h; sourceTree = ""; }; B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryPickerViewController.m; sourceTree = ""; }; B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LanguagePickerViewController.m; sourceTree = ""; }; @@ -1379,6 +1532,28 @@ B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewController.swift; sourceTree = ""; }; B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WidgetPermissionViewController.storyboard; sourceTree = ""; }; B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewModel.swift; sourceTree = ""; }; + B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinatorType.swift; sourceTree = ""; }; + B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinator.swift; sourceTree = ""; }; + B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinatorBridgePresenter.swift; sourceTree = ""; }; + B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCell.swift; sourceTree = ""; }; + B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UserVerificationSessionStatusCell.xib; sourceTree = ""; }; + B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewModelType.swift; sourceTree = ""; }; + B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewModel.swift; sourceTree = ""; }; + B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusCoordinatorType.swift; sourceTree = ""; }; + B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewState.swift; sourceTree = ""; }; + B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewAction.swift; sourceTree = ""; }; + B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewController.swift; sourceTree = ""; }; + B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationSessionsStatusViewController.storyboard; sourceTree = ""; }; + B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusCoordinator.swift; sourceTree = ""; }; + B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserEncryptionTrustLevel.h; sourceTree = ""; }; + B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewState.swift; sourceTree = ""; }; + B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewModelType.swift; sourceTree = ""; }; + B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewModel.swift; sourceTree = ""; }; + B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationSessionStatusViewController.storyboard; sourceTree = ""; }; + B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCoordinatorType.swift; sourceTree = ""; }; + B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewAction.swift; sourceTree = ""; }; + B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewController.swift; sourceTree = ""; }; + B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCoordinator.swift; sourceTree = ""; }; B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPresenter.swift; sourceTree = ""; }; B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorType.swift; sourceTree = ""; }; B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorBridgePresenter.swift; sourceTree = ""; }; @@ -1393,6 +1568,11 @@ B1C45A81232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerCoordinatorBridgePresenter.swift; sourceTree = ""; }; B1C45A82232A8C2600165425 /* SettingsIdentityServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerViewModel.swift; sourceTree = ""; }; B1C45A83232A8C2600165425 /* SettingsIdentityServerViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerViewAction.swift; sourceTree = ""; }; + B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationCellInnerContentView.swift; sourceTree = ""; }; + B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = KeyVerificationCellInnerContentView.xib; sourceTree = ""; }; + B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusBubbleCell.swift; sourceTree = ""; }; + B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationBaseBubbleCell.swift; sourceTree = ""; }; + B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionBubbleCell.swift; sourceTree = ""; }; B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = ""; }; @@ -1409,6 +1589,28 @@ B1C6FFE923954D4B0055347B /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Vector.strings; sourceTree = ""; }; B1CA3A2621EF6913000D1D89 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; B1CA3A2821EF692B000D1D89 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + B1CE83B52422812000D07506 /* KeyVerificationCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationCoordinatorBridgePresenter.swift; sourceTree = ""; }; + B1CE83B72422815900D07506 /* KeyVerificationKind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationKind.swift; sourceTree = ""; }; + B1CE83B82422815A00D07506 /* KeyVerificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationService.swift; sourceTree = ""; }; + B1CE83BC2422817000D07506 /* KeyVerificationVerifiedViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationVerifiedViewController.storyboard; sourceTree = ""; }; + B1CE83BD2422817000D07506 /* KeyVerificationVerifiedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifiedViewController.swift; sourceTree = ""; }; + B1CE83C02422817100D07506 /* KeyVerificationVerifyByScanningViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningViewController.swift; sourceTree = ""; }; + B1CE83C12422817100D07506 /* KeyVerificationVerifyByScanningCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningCoordinator.swift; sourceTree = ""; }; + B1CE83C22422817100D07506 /* KeyVerificationVerifyByScanningViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningViewModelType.swift; sourceTree = ""; }; + B1CE83C32422817100D07506 /* KeyVerificationVerifyByScanningCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningCoordinatorType.swift; sourceTree = ""; }; + B1CE83C42422817100D07506 /* KeyVerificationVerifyByScanningViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningViewAction.swift; sourceTree = ""; }; + B1CE83C52422817100D07506 /* KeyVerificationVerifyByScanningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningViewModel.swift; sourceTree = ""; }; + B1CE83C62422817100D07506 /* KeyVerificationVerifyByScanningViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationVerifyByScanningViewController.storyboard; sourceTree = ""; }; + B1CE83C72422817100D07506 /* KeyVerificationVerifyByScanningViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyByScanningViewState.swift; sourceTree = ""; }; + B1CE83C92422817100D07506 /* KeyVerificationVerifyBySASViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASViewAction.swift; sourceTree = ""; }; + B1CE83CA2422817100D07506 /* KeyVerificationVerifyBySASViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASViewModelType.swift; sourceTree = ""; }; + B1CE83CB2422817100D07506 /* KeyVerificationVerifyBySASCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASCoordinatorType.swift; sourceTree = ""; }; + B1CE83CC2422817100D07506 /* KeyVerificationVerifyBySASViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASViewController.swift; sourceTree = ""; }; + B1CE83CD2422817100D07506 /* KeyVerificationVerifyBySASCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASCoordinator.swift; sourceTree = ""; }; + B1CE83CE2422817100D07506 /* KeyVerificationVerifyBySASViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASViewState.swift; sourceTree = ""; }; + B1CE83D02422817100D07506 /* VerifyEmojiCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerifyEmojiCollectionViewCell.swift; sourceTree = ""; }; + B1CE83D12422817100D07506 /* KeyVerificationVerifyBySASViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyVerificationVerifyBySASViewController.storyboard; sourceTree = ""; }; + B1CE83D22422817100D07506 /* KeyVerificationVerifyBySASViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyVerificationVerifyBySASViewModel.swift; sourceTree = ""; }; B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutAlertPresenter.swift; sourceTree = ""; }; B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionKeysExportPresenter.swift; sourceTree = ""; }; B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuView.swift; sourceTree = ""; }; @@ -1419,6 +1621,7 @@ B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionMenuItemViewData.swift; sourceTree = ""; }; B1D250D62118AA0A000F4E93 /* RoomPredecessorBubbleCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomPredecessorBubbleCell.h; sourceTree = ""; }; B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomPredecessorBubbleCell.m; sourceTree = ""; }; + B1D44998243CD73700959523 /* RoomPowerLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevel.swift; sourceTree = ""; }; B1D4752521EE4E620067973F /* KeyboardAvoider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardAvoider.swift; sourceTree = ""; }; B1D4752621EE4E620067973F /* KeyboardNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNotification.swift; sourceTree = ""; }; B1D4752921EE52B10067973F /* KeyBackupSetupIntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupIntroViewController.swift; sourceTree = ""; }; @@ -1642,21 +1845,6 @@ path = ScreenTemplate; sourceTree = ""; }; - 3232AB94225730E100AD6A5C /* DeviceVerification */ = { - isa = PBXGroup; - children = ( - 324A2046225FC571004FE8B0 /* Incoming */, - 32891D72226728EE00C82226 /* Loading */, - 3232AB96225730E100AD6A5C /* Start */, - 32891D6D2264DF7B00C82226 /* Verified */, - 3232ABAC2257BE6400AD6A5C /* Verify */, - 3232AB95225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift */, - 3232AB9F225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift */, - 3232ABA0225730E100AD6A5C /* DeviceVerificationCoordinator.swift */, - ); - path = DeviceVerification; - sourceTree = ""; - }; 3232AB96225730E100AD6A5C /* Start */ = { isa = PBXGroup; children = ( @@ -1672,30 +1860,6 @@ path = Start; sourceTree = ""; }; - 3232ABAC2257BE6400AD6A5C /* Verify */ = { - isa = PBXGroup; - children = ( - 3232ABBE22594B5C00AD6A5C /* Views */, - 3232ABAD2257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift */, - 3232ABAE2257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard */, - 3232ABAF2257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift */, - 3232ABB02257BE6400AD6A5C /* DeviceVerificationVerifyCoordinator.swift */, - 3232ABB12257BE6400AD6A5C /* DeviceVerificationVerifyViewController.swift */, - 3232ABB22257BE6400AD6A5C /* DeviceVerificationVerifyViewModel.swift */, - 3232ABB32257BE6400AD6A5C /* DeviceVerificationVerifyViewState.swift */, - 3232ABB42257BE6400AD6A5C /* DeviceVerificationVerifyViewAction.swift */, - ); - path = Verify; - sourceTree = ""; - }; - 3232ABBE22594B5C00AD6A5C /* Views */ = { - isa = PBXGroup; - children = ( - 3232ABBF22594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift */, - ); - path = Views; - sourceTree = ""; - }; 323AB944232BD71900C1451F /* Fallback */ = { isa = PBXGroup; children = ( @@ -1720,6 +1884,21 @@ path = Incoming; sourceTree = ""; }; + 32607D63243E0A55006674CC /* PrivateKey */ = { + isa = PBXGroup; + children = ( + 32607D64243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift */, + 32607D65243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift */, + 32607D66243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift */, + 32607D67243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard */, + 32607D68243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift */, + 32607D69243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift */, + 32607D6A243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift */, + 32607D6B243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift */, + ); + path = PrivateKey; + sourceTree = ""; + }; 32863A572384070300D07C4A /* Shared */ = { isa = PBXGroup; children = ( @@ -1747,30 +1926,32 @@ path = SimpleScreenTemplate; sourceTree = ""; }; - 32891D6D2264DF7B00C82226 /* Verified */ = { - isa = PBXGroup; - children = ( - 32891D6E2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard */, - 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */, - ); - path = Verified; - sourceTree = ""; - }; 32891D72226728EE00C82226 /* Loading */ = { isa = PBXGroup; children = ( - 32F6B9632270623000BBA352 /* DeviceVerificationDataLoadingCoordinator.swift */, - 32F6B9662270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift */, - 32F6B9652270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift */, - 32F6B9672270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift */, - 32F6B9682270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift */, - 32F6B9642270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift */, - 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */, - 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */, + 32F6B9662270623100BBA352 /* KeyVerificationDataLoadingCoordinatorType.swift */, + 32F6B9632270623000BBA352 /* KeyVerificationDataLoadingCoordinator.swift */, + 32F6B9652270623100BBA352 /* KeyVerificationDataLoadingViewAction.swift */, + 32F6B9642270623100BBA352 /* KeyVerificationDataLoadingViewState.swift */, + 32F6B9682270623100BBA352 /* KeyVerificationDataLoadingViewModelType.swift */, + 32F6B9672270623100BBA352 /* KeyVerificationDataLoadingViewModel.swift */, + 32891D73226728EE00C82226 /* KeyVerificationDataLoadingViewController.swift */, + 32891D74226728EE00C82226 /* KeyVerificationDataLoadingViewController.storyboard */, ); path = Loading; sourceTree = ""; }; + 3291DC8823E0BE380009732F /* Security */ = { + isa = PBXGroup; + children = ( + 32D5D15C23E1EE2700E3E37C /* ManageSession */, + 3291DC8B23E0BFF10009732F /* SecurityViewController.h */, + 3291DC8C23E0BFF10009732F /* SecurityViewController.m */, + 3291DC8923E0BE820009732F /* Security.storyboard */, + ); + path = Security; + sourceTree = ""; + }; 32935CB21F628B98006888C8 /* js */ = { isa = PBXGroup; children = ( @@ -1828,6 +2009,16 @@ path = KeyBackup; sourceTree = ""; }; + 32D5D15C23E1EE2700E3E37C /* ManageSession */ = { + isa = PBXGroup; + children = ( + 32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */, + 32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */, + 32D5D15F23E1EE2700E3E37C /* ManageSessionViewController.h */, + ); + path = ManageSession; + sourceTree = ""; + }; 32DB556722FDADE50016329E /* ServiceTerms */ = { isa = PBXGroup; children = ( @@ -1911,6 +2102,16 @@ path = Success; sourceTree = ""; }; + B108932623ABE82C00802670 /* BaseContentViews */ = { + isa = PBXGroup; + children = ( + B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */, + B14084C523BF76890010F692 /* BubbleCellContentView.swift */, + B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */, + ); + path = BaseContentViews; + sourceTree = ""; + }; B1098BD921ECE09E000DDA48 /* Generated */ = { isa = PBXGroup; children = ( @@ -2051,6 +2252,21 @@ path = RoomMessageLinkParser; sourceTree = ""; }; + B12D79F223E2426800FACEDC /* Start */ = { + isa = PBXGroup; + children = ( + B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */, + B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */, + B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */, + B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */, + B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */, + B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */, + B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */, + B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */, + ); + path = Start; + sourceTree = ""; + }; B14F142522144F6400FA0595 /* RecoveryKey */ = { isa = PBXGroup; children = ( @@ -2099,6 +2315,49 @@ path = Data; sourceTree = ""; }; + B1550FC52420E8F400CE097B /* QRCode */ = { + isa = PBXGroup; + children = ( + B1550FC62420E8F400CE097B /* Reader */, + B1550FC92420E8F400CE097B /* QRCodeGenerator.swift */, + ); + path = QRCode; + sourceTree = ""; + }; + B1550FC62420E8F400CE097B /* Reader */ = { + isa = PBXGroup; + children = ( + B1550FC72420E8F400CE097B /* QRCodeReaderViewController.storyboard */, + B1550FC82420E8F400CE097B /* QRCodeReaderViewController.swift */, + ); + path = Reader; + sourceTree = ""; + }; + B1550FCF242148FA00CE097B /* KeyVerification */ = { + isa = PBXGroup; + children = ( + B1CE83B4242259ED00D07506 /* Common */, + B1B335122421497000B4A6B5 /* User */, + B1B335112421496800B4A6B5 /* Device */, + ); + path = KeyVerification; + sourceTree = ""; + }; + B157A7AB2445BD85008A5504 /* ScanConfirmation */ = { + isa = PBXGroup; + children = ( + B157A7AC2445BD85008A5504 /* KeyVerificationScanConfirmationViewState.swift */, + B157A7AE2445BD85008A5504 /* KeyVerificationScanConfirmationViewAction.swift */, + B157A7AF2445BD85008A5504 /* KeyVerificationScanConfirmationViewModel.swift */, + B157A7B02445BD85008A5504 /* KeyVerificationScanConfirmationViewModelType.swift */, + B157A7AD2445BD85008A5504 /* KeyVerificationScanConfirmationViewController.swift */, + B157A7B12445BD85008A5504 /* KeyVerificationScanConfirmationViewController.storyboard */, + B157A7B22445BD85008A5504 /* KeyVerificationScanConfirmationCoordinator.swift */, + B157A7B32445BD85008A5504 /* KeyVerificationScanConfirmationCoordinatorType.swift */, + ); + path = ScanConfirmation; + sourceTree = ""; + }; B157FA9623264AE800EBFBD4 /* ThreePidDetails */ = { isa = PBXGroup; children = ( @@ -2370,6 +2629,37 @@ path = OnBoarding; sourceTree = ""; }; + B183226923F59F3E0035B2E8 /* Buttons */ = { + isa = PBXGroup; + children = ( + B183226A23F59F4F0035B2E8 /* Close */, + ); + path = Buttons; + sourceTree = ""; + }; + B183226A23F59F4F0035B2E8 /* Close */ = { + isa = PBXGroup; + children = ( + B183226B23F59F810035B2E8 /* CloseButton.swift */, + ); + path = Close; + sourceTree = ""; + }; + B18DEDAB243372560075FEF7 /* SelfVerifyWait */ = { + isa = PBXGroup; + children = ( + B18DEDD0243377C00075FEF7 /* KeyVerificationSelfVerifyWaitCoordinatorType.swift */, + B18DEDCE243377C00075FEF7 /* KeyVerificationSelfVerifyWaitCoordinator.swift */, + B18DEDD3243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewAction.swift */, + B18DEDD2243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewState.swift */, + B18DEDCC243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewModelType.swift */, + B18DEDCD243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewModel.swift */, + B18DEDD1243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.swift */, + B18DEDCF243377C00075FEF7 /* KeyVerificationSelfVerifyWaitViewController.storyboard */, + ); + path = SelfVerifyWait; + sourceTree = ""; + }; B1963B24228F1C4800CBA17F /* BubbleReactions */ = { isa = PBXGroup; children = ( @@ -2395,6 +2685,33 @@ path = Riot/Modules/Common/CollectionView; sourceTree = SOURCE_ROOT; }; + B1A12C64239AB74500AA2B86 /* CrossSigning */ = { + isa = PBXGroup; + children = ( + ); + path = CrossSigning; + sourceTree = ""; + }; + B1A12C65239ABBDB00AA2B86 /* KeyVerification */ = { + isa = PBXGroup; + children = ( + B108932923ACBA0B00802670 /* SizingViewHeight.swift */, + B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */, + B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */, + B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */, + B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */, + B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */, + B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */, + B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */, + B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */, + B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */, + B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */, + B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */, + B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */, + ); + path = KeyVerification; + sourceTree = ""; + }; B1A6C10523881ECB002882FD /* SlidingModal */ = { isa = PBXGroup; children = ( @@ -2410,12 +2727,48 @@ path = SlidingModal; sourceTree = ""; }; + B1B335112421496800B4A6B5 /* Device */ = { + isa = PBXGroup; + children = ( + B1B336B5242B718B00F95EC4 /* SelfVerifyStart */, + B18DEDAB243372560075FEF7 /* SelfVerifyWait */, + 324A2046225FC571004FE8B0 /* Incoming */, + 3232AB96225730E100AD6A5C /* Start */, + ); + path = Device; + sourceTree = ""; + }; + B1B335122421497000B4A6B5 /* User */ = { + isa = PBXGroup; + children = ( + B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */, + B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */, + B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */, + B12D79F223E2426800FACEDC /* Start */, + B1BEE71723DF2B8A0003A4CB /* SessionsStatus */, + B1BEE73D23E08AC30003A4CB /* SessionStatus */, + ); + path = User; + sourceTree = ""; + }; + B1B336B5242B718B00F95EC4 /* SelfVerifyStart */ = { + isa = PBXGroup; + children = ( + B1B336B9242B933500F95EC4 /* KeyVerificationSelfVerifyStartCoordinatorType.swift */, + B1B336BB242B933500F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift */, + B1B336BC242B933600F95EC4 /* KeyVerificationSelfVerifyStartViewAction.swift */, + B1B336B6242B933300F95EC4 /* KeyVerificationSelfVerifyStartViewState.swift */, + B1B336B8242B933400F95EC4 /* KeyVerificationSelfVerifyStartViewModelType.swift */, + B1B336BD242B933600F95EC4 /* KeyVerificationSelfVerifyStartViewModel.swift */, + B1B336B7242B933300F95EC4 /* KeyVerificationSelfVerifyStartViewController.swift */, + B1B336BA242B933500F95EC4 /* KeyVerificationSelfVerifyStartViewController.storyboard */, + ); + path = SelfVerifyStart; + sourceTree = ""; + }; B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( - B1A6C10523881ECB002882FD /* SlidingModal */, - 32DB556722FDADE50016329E /* ServiceTerms */, - 3232AB94225730E100AD6A5C /* DeviceVerification */, B1B556EA20EE6C4C00210D55 /* Main */, B1B556CA20EE6C4C00210D55 /* TabBar */, B1B556F920EE6C4C00210D55 /* Authentication */, @@ -2440,6 +2793,11 @@ B1B556FD20EE6C4C00210D55 /* RoomKeyRequest */, B1B556B020EE6C4C00210D55 /* BugReport */, B1098BE921ECFE64000DDA48 /* KeyBackup */, + B1550FCF242148FA00CE097B /* KeyVerification */, + B1A12C64239AB74500AA2B86 /* CrossSigning */, + B1A6C10523881ECB002882FD /* SlidingModal */, + 32DB556722FDADE50016329E /* ServiceTerms */, + B1550FC52420E8F400CE097B /* QRCode */, B1B556CD20EE6C4C00210D55 /* Common */, ); path = Modules; @@ -2457,6 +2815,7 @@ B1B5567B20EE6C4C00210D55 /* Language */, B1B5567820EE6C4C00210D55 /* PhoneCountry */, B1B5568020EE6C4C00210D55 /* DeactivateAccount */, + 3291DC8823E0BE380009732F /* Security */, B1CE9EFB22148681000FAE6A /* SignOut */, ); path = Settings; @@ -2610,6 +2969,7 @@ B1B556A620EE6C4C00210D55 /* Detail */ = { isa = PBXGroup; children = ( + B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */, B1B556A720EE6C4C00210D55 /* RoomMemberDetailsViewController.h */, B1B556A820EE6C4C00210D55 /* RoomMemberDetailsViewController.m */, B1B556A920EE6C4C00210D55 /* RoomMemberDetailsViewController.xib */, @@ -2715,6 +3075,7 @@ B1B556CD20EE6C4C00210D55 /* Common */ = { isa = PBXGroup; children = ( + B183226923F59F3E0035B2E8 /* Buttons */, B1963B3622933B9500CBA17F /* CollectionView */, B1B556CE20EE6C4C00210D55 /* WebViewController */, B1B556D120EE6C4C00210D55 /* NavigationController */, @@ -3249,6 +3610,8 @@ B1B5583F20EF768E00210D55 /* BubbleCells */ = { isa = PBXGroup; children = ( + B108932623ABE82C00802670 /* BaseContentViews */, + B1A12C65239ABBDB00AA2B86 /* KeyVerification */, B1B5584220EF768E00210D55 /* Encryption */, B1B5589220EF768E00210D55 /* RoomEmptyBubbleCell.h */, B1B558B620EF768E00210D55 /* RoomEmptyBubbleCell.m */, @@ -3564,10 +3927,44 @@ path = ReactionHistory; sourceTree = ""; }; + B1BEE71723DF2B8A0003A4CB /* SessionsStatus */ = { + isa = PBXGroup; + children = ( + B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */, + B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */, + B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */, + B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */, + B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */, + B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */, + B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */, + B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */, + B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */, + B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */, + ); + path = SessionsStatus; + sourceTree = ""; + }; + B1BEE73D23E08AC30003A4CB /* SessionStatus */ = { + isa = PBXGroup; + children = ( + B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */, + B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */, + B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */, + B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */, + B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */, + B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */, + B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */, + B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */, + ); + path = SessionStatus; + sourceTree = ""; + }; B1C3361A22F328AE0021BA8D /* Camera */ = { isa = PBXGroup; children = ( B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */, + B183226723F561380035B2E8 /* CameraAccessAlertPresenter.swift */, + B183226523F55D6B0035B2E8 /* CameraAccessManager.swift */, ); path = Camera; sourceTree = ""; @@ -3605,6 +4002,80 @@ path = ContextualMenu; sourceTree = ""; }; + B1CE83B4242259ED00D07506 /* Common */ = { + isa = PBXGroup; + children = ( + 32891D72226728EE00C82226 /* Loading */, + B1CE83BE2422817100D07506 /* Verify */, + B157A7AB2445BD85008A5504 /* ScanConfirmation */, + B1CE83BB2422817000D07506 /* Verified */, + B1CE83B72422815900D07506 /* KeyVerificationKind.swift */, + B18DEDDC2433967C0075FEF7 /* KeyVerificationFlow.swift */, + B1CE83B82422815A00D07506 /* KeyVerificationService.swift */, + 3232AB95225730E100AD6A5C /* KeyVerificationCoordinatorType.swift */, + 3232ABA0225730E100AD6A5C /* KeyVerificationCoordinator.swift */, + B1CE83B52422812000D07506 /* KeyVerificationCoordinatorBridgePresenter.swift */, + ); + path = Common; + sourceTree = ""; + }; + B1CE83BB2422817000D07506 /* Verified */ = { + isa = PBXGroup; + children = ( + B1CE83BC2422817000D07506 /* KeyVerificationVerifiedViewController.storyboard */, + B1CE83BD2422817000D07506 /* KeyVerificationVerifiedViewController.swift */, + ); + path = Verified; + sourceTree = ""; + }; + B1CE83BE2422817100D07506 /* Verify */ = { + isa = PBXGroup; + children = ( + B1CE83BF2422817100D07506 /* Scanning */, + B1CE83C82422817100D07506 /* SAS */, + ); + path = Verify; + sourceTree = ""; + }; + B1CE83BF2422817100D07506 /* Scanning */ = { + isa = PBXGroup; + children = ( + B1CE83C32422817100D07506 /* KeyVerificationVerifyByScanningCoordinatorType.swift */, + B1CE83C12422817100D07506 /* KeyVerificationVerifyByScanningCoordinator.swift */, + B1CE83C42422817100D07506 /* KeyVerificationVerifyByScanningViewAction.swift */, + B1CE83C72422817100D07506 /* KeyVerificationVerifyByScanningViewState.swift */, + B1CE83C22422817100D07506 /* KeyVerificationVerifyByScanningViewModelType.swift */, + B1CE83C52422817100D07506 /* KeyVerificationVerifyByScanningViewModel.swift */, + B1CE83C02422817100D07506 /* KeyVerificationVerifyByScanningViewController.swift */, + B1CE83C62422817100D07506 /* KeyVerificationVerifyByScanningViewController.storyboard */, + ); + path = Scanning; + sourceTree = ""; + }; + B1CE83C82422817100D07506 /* SAS */ = { + isa = PBXGroup; + children = ( + B1CE83CF2422817100D07506 /* Views */, + B1CE83CB2422817100D07506 /* KeyVerificationVerifyBySASCoordinatorType.swift */, + B1CE83CD2422817100D07506 /* KeyVerificationVerifyBySASCoordinator.swift */, + B1CE83C92422817100D07506 /* KeyVerificationVerifyBySASViewAction.swift */, + B1CE83CE2422817100D07506 /* KeyVerificationVerifyBySASViewState.swift */, + B1CE83CA2422817100D07506 /* KeyVerificationVerifyBySASViewModelType.swift */, + B1CE83D22422817100D07506 /* KeyVerificationVerifyBySASViewModel.swift */, + B1CE83CC2422817100D07506 /* KeyVerificationVerifyBySASViewController.swift */, + B1CE83D12422817100D07506 /* KeyVerificationVerifyBySASViewController.storyboard */, + ); + path = SAS; + sourceTree = ""; + }; + B1CE83CF2422817100D07506 /* Views */ = { + isa = PBXGroup; + children = ( + B1CE83D02422817100D07506 /* VerifyEmojiCollectionViewCell.swift */, + ); + path = Views; + sourceTree = ""; + }; B1CE9EFB22148681000FAE6A /* SignOut */ = { isa = PBXGroup; children = ( @@ -3670,6 +4141,7 @@ B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */, B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */, B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */, + 32607D63243E0A55006674CC /* PrivateKey */, B1E5368A21FB6FC0001F3AFF /* Passphrase */, B14F142522144F6400FA0595 /* RecoveryKey */, B1107EC62200B0190038014B /* Success */, @@ -3798,6 +4270,7 @@ F083BBFC1E7009EC00A9B29C /* RoomEmailInvitation.m */, F083BBFD1E7009EC00A9B29C /* RoomPreviewData.h */, F083BBFE1E7009EC00A9B29C /* RoomPreviewData.m */, + B1D44998243CD73700959523 /* RoomPowerLevel.swift */, ); path = Room; sourceTree = ""; @@ -3812,6 +4285,7 @@ F083BC141E7009EC00A9B29C /* Tools.h */, F083BC151E7009EC00A9B29C /* Tools.m */, B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */, + B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */, ); path = Utils; sourceTree = ""; @@ -4051,7 +4525,6 @@ files = ( B1B5590220EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib in Resources */, B1B558CA20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, - 32891D702264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard in Resources */, B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */, F083BDE91E7009ED00A9B29C /* ring.mp3 in Resources */, B1B558F720EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, @@ -4061,6 +4534,8 @@ B1A6C113238BD245002882FD /* SlidingModalContainerView.xib in Resources */, B1B5594420EF7BD000210D55 /* TableViewCellWithCollectionView.xib in Resources */, B1B558D520EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, + B12D79FC23E2462200FACEDC /* UserVerificationStartViewController.storyboard in Resources */, + B1CE83D32422817200D07506 /* KeyVerificationVerifiedViewController.storyboard in Resources */, B1B5590020EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, B1B5597020EFA85D00210D55 /* EncryptionInfoView.xib in Resources */, B1B558BC20EF768F00210D55 /* RoomMembershipBubbleCell.xib in Resources */, @@ -4077,12 +4552,14 @@ B1B558EF20EF768F00210D55 /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib in Resources */, B1B5573420EE6C4D00210D55 /* GroupDetailsViewController.xib in Resources */, B1B557E420EF60B900210D55 /* MessagesSearchResultAttachmentBubbleCell.xib in Resources */, + B1B336C2242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewController.storyboard in Resources */, B1B558F220EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, B1B558EA20EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B1B558CD20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */, B1B9DEEA22EB34EF0065E677 /* ReactionHistoryViewController.storyboard in Resources */, B1B9194C2118984300FE25B5 /* RoomPredecessorBubbleCell.xib in Resources */, B1C562E9228C7CF20037F12A /* ContextualMenuItemView.xib in Resources */, + B1BEE73A23DF44A60003A4CB /* UserVerificationSessionsStatusViewController.storyboard in Resources */, B1B5572120EE6C4D00210D55 /* ContactsTableViewController.xib in Resources */, B1B5593A20EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.xib in Resources */, B1B558D820EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */, @@ -4112,9 +4589,11 @@ B1B558EC20EF768F00210D55 /* RoomMembershipCollapsedBubbleCell.xib in Resources */, B1B558D720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B1B5590820EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.xib in Resources */, + B18DEDD7243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.storyboard in Resources */, F083BDE81E7009ED00A9B29C /* message.caf in Resources */, B1107ECA2200B09F0038014B /* KeyBackupRecoverSuccessViewController.storyboard in Resources */, B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */, + B1550FCA2420E8F500CE097B /* QRCodeReaderViewController.storyboard in Resources */, B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */, F083BE011E7009ED00A9B29C /* third_party_licenses.html in Resources */, B1098BFC21ECFE65000DDA48 /* PasswordStrengthView.xib in Resources */, @@ -4122,10 +4601,12 @@ B110872421F098F0003554A5 /* ActivityIndicatorView.xib in Resources */, B1B5573320EE6C4D00210D55 /* GroupHomeViewController.xib in Resources */, 3232AB2122564D9100AD6A5C /* README.md in Resources */, + 3291DC8A23E0BE820009732F /* Security.storyboard in Resources */, B1B5593920EF7BAC00210D55 /* TableViewCellWithCheckBoxes.xib in Resources */, B1B557C120EF5B4500210D55 /* DisabledRoomInputToolbarView.xib in Resources */, B1DCC61722E5E17100625807 /* EmojiPickerViewController.storyboard in Resources */, 32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */, + 32D5D16123E1EE2700E3E37C /* ManageSession.storyboard in Resources */, B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */, B1664DA320F4F96200808783 /* Vector.strings in Resources */, B1B557C720EF5CD400210D55 /* DirectoryServerDetailTableViewCell.xib in Resources */, @@ -4133,10 +4614,12 @@ 3232AB1522564D9100AD6A5C /* flat-swift4-vector.stencil in Resources */, F083BDE61E7009ED00A9B29C /* busy.mp3 in Resources */, B1B5574C20EE6C4D00210D55 /* MediaAlbumContentViewController.xib in Resources */, + 32607D6F243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard in Resources */, B1B557E820EF60F500210D55 /* MessagesSearchResultTextMsgBubbleCell.xib in Resources */, B1B558D920EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.xib in Resources */, B1B5573020EE6C4D00210D55 /* BugReportViewController.xib in Resources */, B169329B20F39E6300746532 /* Main.storyboard in Resources */, + B1CE83DB2422817200D07506 /* KeyVerificationVerifyByScanningViewController.storyboard in Resources */, 3232AB1422564D9100AD6A5C /* swiftgen-config.yml in Resources */, 324A204F225FC571004FE8B0 /* DeviceVerificationIncomingViewController.storyboard in Resources */, 3232AB4B2256558300AD6A5C /* TemplateScreenViewController.storyboard in Resources */, @@ -4150,10 +4633,12 @@ B1B558D120EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, B1B558FE20EF768F00210D55 /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib in Resources */, B1B5581D20EF625800210D55 /* RoomAvatarTitleView.xib in Resources */, + B1BEE74923E093260003A4CB /* UserVerificationSessionStatusViewController.storyboard in Resources */, B1B5590B20EF768F00210D55 /* RoomMembershipExpandedBubbleCell.xib in Resources */, B1B558E720EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, B1B558DC20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B1B5572E20EE6C4D00210D55 /* ReadReceiptsViewController.xib in Resources */, + B1BEE72B23DF38B20003A4CB /* UserVerificationSessionStatusCell.xib in Resources */, B1B5574220EE6C4D00210D55 /* RecentsViewController.xib in Resources */, B1B5571C20EE6C4D00210D55 /* DeactivateAccountViewController.storyboard in Resources */, B1B5596520EF9E9B00210D55 /* RoomTableViewCell.xib in Resources */, @@ -4174,8 +4659,10 @@ B1B5573920EE6C4D00210D55 /* GroupRoomsViewController.xib in Resources */, B1B5597620EFB02A00210D55 /* InviteRecentTableViewCell.xib in Resources */, B1B558FB20EF768F00210D55 /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, + B157A7B92445BD86008A5504 /* KeyVerificationScanConfirmationViewController.storyboard in Resources */, B1B558BB20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, B1B557A720EF5A1B00210D55 /* DeviceTableViewCell.xib in Resources */, + B14084C823BF76CB0010F692 /* BubbleCellContentView.xib in Resources */, B1B557D220EF5E3500210D55 /* MediaAlbumTableCell.xib in Resources */, B1B558C520EF768F00210D55 /* RoomOutgoingEncryptedTextMsgBubbleCell.xib in Resources */, B1B5582B20EF666100210D55 /* DirectoryRecentTableViewCell.xib in Resources */, @@ -4186,7 +4673,6 @@ B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */, B1B557DD20EF5FBB00210D55 /* FilesSearchTableViewCell.xib in Resources */, B1B5590320EF768F00210D55 /* RoomSelectedStickerBubbleCell.xib in Resources */, - 3232ABB62257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard in Resources */, B1B5573F20EE6C4D00210D55 /* SegmentedViewController.xib in Resources */, B1B5581E20EF625800210D55 /* RoomTitleView.xib in Resources */, B1B558C020EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, @@ -4198,12 +4684,14 @@ B1B557A220EF58AD00210D55 /* ContactTableViewCell.xib in Resources */, B1B558EB20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B10B3B5C2201DD740072C76B /* KeyBackupBannerCell.xib in Resources */, - 32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */, + B1C543A6239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib in Resources */, + 32891D76226728EF00C82226 /* KeyVerificationDataLoadingViewController.storyboard in Resources */, B1B5581820EF625800210D55 /* PreviewRoomTitleView.xib in Resources */, B1B5583020EF66BA00210D55 /* RoomIdOrAliasTableViewCell.xib in Resources */, B1B558BF20EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.xib in Resources */, B1B558FD20EF768F00210D55 /* RoomIncomingAttachmentBubbleCell.xib in Resources */, B1B558DB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib in Resources */, + B1CE83E42422817200D07506 /* KeyVerificationVerifyBySASViewController.storyboard in Resources */, B1B5572B20EE6C4D00210D55 /* RoomMemberDetailsViewController.xib in Resources */, B1D4752C21EE52C30067973F /* KeyBackupSetupIntroViewController.storyboard in Resources */, B1B558C620EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.xib in Resources */, @@ -4407,8 +4895,10 @@ buildActionMask = 2147483647; files = ( B1B557D120EF5E3500210D55 /* MediaAlbumTableCell.m in Sources */, + 32607D71243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift in Sources */, 324A2053225FC571004FE8B0 /* DeviceVerificationIncomingViewModel.swift in Sources */, B1B557A120EF58AD00210D55 /* ContactTableViewCell.m in Sources */, + B1CE83DE2422817200D07506 /* KeyVerificationVerifyBySASViewModelType.swift in Sources */, F083BE021E7009ED00A9B29C /* AvatarGenerator.m in Sources */, B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */, B1B5573A20EE6C4D00210D55 /* GroupRoomsViewController.m in Sources */, @@ -4417,6 +4907,7 @@ B1B5573120EE6C4D00210D55 /* BugReportViewController.m in Sources */, 324A2051225FC571004FE8B0 /* DeviceVerificationIncomingViewState.swift in Sources */, B16932A520F3A21C00746532 /* empty.mm in Sources */, + B157A7B52445BD86008A5504 /* KeyVerificationScanConfirmationViewController.swift in Sources */, 3232AB4A2256558300AD6A5C /* FlowTemplateCoordinator.swift in Sources */, B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */, B1B9DEDE22E9D9890065E677 /* EmojiServiceType.swift in Sources */, @@ -4430,24 +4921,27 @@ B1B5598820EFC3E000210D55 /* WidgetManager.m in Sources */, B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */, B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */, + 32607D6C243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift in Sources */, B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */, B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */, B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */, - 3232ABB72257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift in Sources */, - 32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */, + 32F6B96D2270623100BBA352 /* KeyVerificationDataLoadingViewModel.swift in Sources */, B16932B120F3AC9200746532 /* RoomSearchDataSource.m in Sources */, B16932A320F3A21C00746532 /* main.m in Sources */, B1B5574520EE6C4D00210D55 /* StartChatViewController.m in Sources */, 3232AB4C2256558300AD6A5C /* TemplateScreenCoordinator.swift in Sources */, B1B5575920EE6C4D00210D55 /* HomeMessagesSearchViewController.m in Sources */, B1B558DE20EF768F00210D55 /* RoomIncomingAttachmentBubbleCell.m in Sources */, + B1BEE74D23E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift in Sources */, B1B5574820EE6C4D00210D55 /* PeopleViewController.m in Sources */, B1B5598720EFC3E000210D55 /* Widget.m in Sources */, B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */, B1B557E320EF60B900210D55 /* MessagesSearchResultAttachmentBubbleCell.m in Sources */, B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */, - 3232ABAA225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift in Sources */, + 32607D72243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift in Sources */, B1B5574420EE6C4D00210D55 /* CallViewController.m in Sources */, + B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */, + B157A7B62445BD86008A5504 /* KeyVerificationScanConfirmationViewAction.swift in Sources */, B1B5572220EE6C4D00210D55 /* RoomSettingsViewController.m in Sources */, B1B5577320EE702800210D55 /* JitsiViewController.m in Sources */, B169331620F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m in Sources */, @@ -4456,21 +4950,26 @@ 32242F1221E8FBA900725742 /* ThemeService.m in Sources */, B1B558E820EF768F00210D55 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.m in Sources */, B1B558F320EF768F00210D55 /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, + B12D79FE23E2462200FACEDC /* UserVerificationStartViewController.swift in Sources */, + 32607D73243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift in Sources */, B1B557BD20EF5B4500210D55 /* KeyboardGrowingTextView.m in Sources */, B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */, B1B558F420EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1B5572320EE6C4D00210D55 /* AttachmentsViewController.m in Sources */, + B18DEDDD2433967C0075FEF7 /* KeyVerificationFlow.swift in Sources */, F083BDEE1E7009ED00A9B29C /* MXRoom+Riot.m in Sources */, B120863722EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift in Sources */, B1B5598620EFC3E000210D55 /* RiotSettings.swift in Sources */, + B1CE83D52422817200D07506 /* KeyVerificationVerifyByScanningViewController.swift in Sources */, 3232ABA3225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift in Sources */, 3232AB4D2256558300AD6A5C /* TemplateScreenCoordinatorType.swift in Sources */, B1B5581720EF625800210D55 /* PreviewRoomTitleView.m in Sources */, B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */, B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */, B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */, + B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */, + 32607D70243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift in Sources */, B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, - 3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */, B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */, B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */, B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */, @@ -4481,6 +4980,8 @@ B169330B20F3CA3A00746532 /* Contact.m in Sources */, B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */, B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */, + B108931F23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift in Sources */, + B1CE83D42422817200D07506 /* KeyVerificationVerifiedViewController.swift in Sources */, B1B5599220EFC5E400210D55 /* Analytics.m in Sources */, B14F143422144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift in Sources */, B1098BF621ECFE65000DDA48 /* KeyBackupSetupPassphraseCoordinator.swift in Sources */, @@ -4492,10 +4993,12 @@ B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */, B1B5577420EE702900210D55 /* WidgetViewController.m in Sources */, B1DCC63122E7026F00625807 /* EmojiPickerHeaderView.swift in Sources */, + B1CE83DC2422817200D07506 /* KeyVerificationVerifyByScanningViewState.swift in Sources */, B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */, B1C45A8C232A8C2600165425 /* SettingsIdentityServerViewAction.swift in Sources */, 32A6001E22C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift in Sources */, B1B5574A20EE6C4D00210D55 /* MediaPickerViewController.m in Sources */, + B1BEE74623E093260003A4CB /* UserVerificationSessionStatusViewState.swift in Sources */, B1B5598520EFC3E000210D55 /* RageShakeManager.m in Sources */, B1A6C111238BD236002882FD /* SlidingModalContainerView.swift in Sources */, B1DCC62D22E61EAF00625807 /* EmojiPickerViewCell.swift in Sources */, @@ -4503,13 +5006,16 @@ B1B558D420EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, B169331420F3CAFC00746532 /* PublicRoomTableViewCell.m in Sources */, 32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */, + 3291DC8D23E0BFF10009732F /* SecurityViewController.m in Sources */, + B18DEDD4243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewModelType.swift in Sources */, B1C45A88232A8C2600165425 /* SettingsIdentityServerViewState.swift in Sources */, B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */, 32DB557822FDADE50016329E /* ServiceTermsModalScreenViewState.swift in Sources */, B1B558E120EF768F00210D55 /* RoomMembershipCollapsedBubbleCell.m in Sources */, B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */, B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */, - 32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */, + 32F6B9692270623100BBA352 /* KeyVerificationDataLoadingCoordinator.swift in Sources */, + B157A7BA2445BD86008A5504 /* KeyVerificationScanConfirmationCoordinator.swift in Sources */, B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */, 32863A5A2384070300D07C4A /* RiotSharedSettings.swift in Sources */, B1B5594720EF7BD000210D55 /* RoomCollectionViewCell.m in Sources */, @@ -4520,7 +5026,9 @@ B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */, B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */, B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */, + B1BEE74C23E093260003A4CB /* UserVerificationSessionStatusViewController.swift in Sources */, B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */, + B1C543B023A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift in Sources */, B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */, B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */, B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */, @@ -4530,29 +5038,31 @@ B1B9DEE822EB34EF0065E677 /* ReactionHistoryCoordinatorType.swift in Sources */, B14F143122144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewState.swift in Sources */, 32DB557F22FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift in Sources */, + B157A7B72445BD86008A5504 /* KeyVerificationScanConfirmationViewModel.swift in Sources */, B1098C1121ED07E4000DDA48 /* NavigationRouterType.swift in Sources */, B1B5573D20EE6C4D00210D55 /* WebViewViewController.m in Sources */, 3209451221F1C1430088CAA2 /* BlackTheme.swift in Sources */, B1B5572720EE6C4D00210D55 /* RoomSearchViewController.m in Sources */, - 3232ABBC2257BE6500AD6A5C /* DeviceVerificationVerifyViewAction.swift in Sources */, + B14084C623BF76890010F692 /* BubbleCellContentView.swift in Sources */, F05927C91FDED836009F2A68 /* MXGroup+Riot.m in Sources */, B1B5594520EF7BD000210D55 /* TableViewCellWithCollectionView.m in Sources */, B1A6C109238828A6002882FD /* SlidingModalPresentationDelegate.swift in Sources */, 32DB557722FDADE50016329E /* ServiceTermsModalCoordinator.swift in Sources */, 32DB557922FDADE50016329E /* ServiceTermsModalScreenViewModel.swift in Sources */, - 32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */, - 32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */, + 32891D75226728EE00C82226 /* KeyVerificationDataLoadingViewController.swift in Sources */, F083BDEF1E7009ED00A9B29C /* UINavigationController+Riot.m in Sources */, B1B5581F20EF625800210D55 /* SimpleRoomTitleView.m in Sources */, B1C562E2228C7C8D0037F12A /* RoomContextualMenuViewController.swift in Sources */, B169330020F3C97D00746532 /* RoomDataSource.m in Sources */, B1B558ED20EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.m in Sources */, B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */, + B183226823F561380035B2E8 /* CameraAccessAlertPresenter.swift in Sources */, 3232AB512256558300AD6A5C /* TemplateScreenViewAction.swift in Sources */, 3232AB4E2256558300AD6A5C /* TemplateScreenViewModelType.swift in Sources */, 32DB557D22FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift in Sources */, B1B5590520EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m in Sources */, 3232ABA5225730E100AD6A5C /* DeviceVerificationStartViewModelType.swift in Sources */, + B1B336C1242B933700F95EC4 /* KeyVerificationSelfVerifyStartCoordinatorType.swift in Sources */, B1B558DD20EF768F00210D55 /* RoomIncomingEncryptedTextMsgBubbleCell.m in Sources */, B1098BE521ECE1FC000DDA48 /* Storyboards.swift in Sources */, 3232ABC2225B996200AD6A5C /* Themable.swift in Sources */, @@ -4561,46 +5071,54 @@ B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */, B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */, B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */, + B18DEDD6243377C10075FEF7 /* KeyVerificationSelfVerifyWaitCoordinator.swift in Sources */, 32DB557A22FDADE50016329E /* ServiceTermsModalScreenViewController.swift in Sources */, B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */, B1B5573C20EE6C4D00210D55 /* MasterTabBarController.m in Sources */, B1DCC61B22E5E17100625807 /* EmojiPickerCoordinator.swift in Sources */, - 32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */, + 32F6B96E2270623100BBA352 /* KeyVerificationDataLoadingViewModelType.swift in Sources */, B1B5592C20EF7A5D00210D55 /* TableViewCellWithButton.m in Sources */, 32242F1421E8FBA900725742 /* DefaultTheme.swift in Sources */, B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */, 32242F1321E8FBA900725742 /* Theme.swift in Sources */, + B108932523AB93A200802670 /* KeyVerificationConclusionViewData.swift in Sources */, B1B5582520EF638A00210D55 /* RoomMemberTitleView.m in Sources */, B1B5582C20EF666100210D55 /* DirectoryRecentTableViewCell.m in Sources */, B1B558E420EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */, + B18DEDD8243377C10075FEF7 /* KeyVerificationSelfVerifyWaitCoordinatorType.swift in Sources */, B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */, B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */, - 3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */, + B108932323AB908A00802670 /* KeyVerificationRequestStatusViewData.swift in Sources */, B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */, B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */, B1B5572A20EE6C4D00210D55 /* RoomMemberDetailsViewController.m in Sources */, B1B5590120EF768F00210D55 /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */, - 32F6B96B2270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift in Sources */, + 32F6B96B2270623100BBA352 /* KeyVerificationDataLoadingViewAction.swift in Sources */, B1B558C920EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, B1B5571B20EE6C4D00210D55 /* DeactivateAccountViewController.m in Sources */, B1DCC63922E85E9A00625807 /* EmojiMartStore.swift in Sources */, B1B5590620EF768F00210D55 /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m in Sources */, B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */, B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */, + B1CE83D92422817200D07506 /* KeyVerificationVerifyByScanningViewAction.swift in Sources */, B1C45A8B232A8C2600165425 /* SettingsIdentityServerViewModel.swift in Sources */, + B12D7A0123E2462200FACEDC /* UserVerificationStartViewModel.swift in Sources */, B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */, + B1BEE73623DF44A60003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift in Sources */, B1C45A86232A8C2600165425 /* SettingsIdentityServerViewModelType.swift in Sources */, F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */, B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */, B1DCC62422E60CA900625807 /* EmojiPickerCategoryViewData.swift in Sources */, + B1550FCB2420E8F500CE097B /* QRCodeReaderViewController.swift in Sources */, 324A2056225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift in Sources */, B16932F720F3C50E00746532 /* RecentsDataSource.m in Sources */, B1DCC63B22E85EF800625807 /* EmojiMartCategory.swift in Sources */, 3232AB4F2256558300AD6A5C /* TemplateScreenViewController.swift in Sources */, B1B558FC20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B5572920EE6C4D00210D55 /* RoomFilesViewController.m in Sources */, - 3232ABBA2257BE6500AD6A5C /* DeviceVerificationVerifyViewModel.swift in Sources */, + B1BEE74B23E093260003A4CB /* UserVerificationSessionStatusViewAction.swift in Sources */, B1098C1021ED07E4000DDA48 /* Presentable.swift in Sources */, + B1BEE73923DF44A60003A4CB /* UserVerificationSessionsStatusViewController.swift in Sources */, B1B558E020EF768F00210D55 /* RoomOutgoingTextMsgBubbleCell.m in Sources */, B1C562E3228C7C8D0037F12A /* RoomContextualMenuPresenter.swift in Sources */, B1B5593C20EF7BAC00210D55 /* TableViewCellWithCheckBoxes.m in Sources */, @@ -4608,10 +5126,12 @@ B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */, 322C110822BBC6F80043FEAC /* WidgetManagerConfig.swift in Sources */, F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */, + B1CE83B92422815C00D07506 /* KeyVerificationKind.swift in Sources */, + B1BEE71423DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift in Sources */, B1B5596F20EFA85D00210D55 /* EncryptionInfoView.m in Sources */, B1B5573820EE6C4D00210D55 /* GroupParticipantsViewController.m in Sources */, - 3232ABBB2257BE6500AD6A5C /* DeviceVerificationVerifyViewState.swift in Sources */, - 3232ABAB225730E100AD6A5C /* DeviceVerificationCoordinator.swift in Sources */, + 3232ABAB225730E100AD6A5C /* KeyVerificationCoordinator.swift in Sources */, + B1BEE73B23DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift in Sources */, B1B5583E20EF6E7F00210D55 /* GroupRoomTableViewCell.m in Sources */, B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */, B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */, @@ -4619,21 +5139,29 @@ 32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */, B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */, B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */, + B197B7C6243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift in Sources */, + B12D79FD23E2462200FACEDC /* UserVerificationStartViewModelType.swift in Sources */, + B1C543AE23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift in Sources */, B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */, B1B5579120EF568D00210D55 /* GroupInviteTableViewCell.m in Sources */, B1B5579A20EF575B00210D55 /* ForgotPasswordInputsView.m in Sources */, B1B12B2922942315002CB419 /* UITouch.swift in Sources */, B1B558CC20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, + B183226623F55D6B0035B2E8 /* CameraAccessManager.swift in Sources */, B1B5571D20EE6C4D00210D55 /* HomeViewController.m in Sources */, + B1CE83DD2422817200D07506 /* KeyVerificationVerifyBySASViewAction.swift in Sources */, B1C45A84232A8C2600165425 /* SettingsIdentityServerCoordinatorType.swift in Sources */, B1DCC63722E8541700625807 /* EmojiStore.swift in Sources */, 3232ABA6225730E100AD6A5C /* DeviceVerificationStartViewController.swift in Sources */, B16932EA20F3C39000746532 /* UnifiedSearchRecentsDataSource.m in Sources */, + B1BEE72A23DF38B20003A4CB /* UserVerificationSessionStatusCell.swift in Sources */, B1C45A8A232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift in Sources */, B1B557DE20EF5FBB00210D55 /* FilesSearchTableViewCell.m in Sources */, B1B5574020EE6C4D00210D55 /* SegmentedViewController.m in Sources */, B1B5599320EFC5E400210D55 /* DecryptionFailure.m in Sources */, + B1CE83E12422817200D07506 /* KeyVerificationVerifyBySASCoordinator.swift in Sources */, B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */, + B108932A23ACBA0B00802670 /* SizingViewHeight.swift in Sources */, B157FAA323264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift in Sources */, B1098BF921ECFE65000DDA48 /* KeyBackupSetupCoordinator.swift in Sources */, B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */, @@ -4650,17 +5178,25 @@ 32A6001D22C661100042C1D9 /* EditHistoryCoordinatorType.swift in Sources */, F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */, B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */, + B1BEE73823DF44A60003A4CB /* UserVerificationSessionsStatusViewAction.swift in Sources */, B1B5577E20EE84BF00210D55 /* IncomingCallView.m in Sources */, + B1CE83E22422817200D07506 /* KeyVerificationVerifyBySASViewState.swift in Sources */, B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */, + B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift in Sources */, + B1CE83D62422817200D07506 /* KeyVerificationVerifyByScanningCoordinator.swift in Sources */, B1B5578F20EF568D00210D55 /* GroupTableViewCell.m in Sources */, + 32D5D16023E1EE2700E3E37C /* ManageSessionViewController.m in Sources */, B1B5573220EE6C4D00210D55 /* GroupHomeViewController.m in Sources */, B1B5595220EF9A8700210D55 /* RecentTableViewCell.m in Sources */, - 32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */, + 32F6B96C2270623100BBA352 /* KeyVerificationDataLoadingCoordinatorType.swift in Sources */, + B14084CA23BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift in Sources */, + B157A7B42445BD86008A5504 /* KeyVerificationScanConfirmationViewState.swift in Sources */, B1DCC61D22E5E17100625807 /* EmojiPickerViewModelType.swift in Sources */, B1B5574120EE6C4D00210D55 /* RecentsViewController.m in Sources */, B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */, B1B5577120EE702800210D55 /* StickerPickerViewController.m in Sources */, 32FDC1CD2386CD390084717A /* RiotSettingIntegrationProvisioning.swift in Sources */, + B1BEE73523DF44A60003A4CB /* UserVerificationSessionsStatusViewModel.swift in Sources */, B104C2942203773C00D9F496 /* KeyBackupBannerPreferences.swift in Sources */, B1B5572020EE6C4D00210D55 /* ContactsTableViewController.m in Sources */, B1B5581920EF625800210D55 /* RoomTitleView.m in Sources */, @@ -4674,9 +5210,12 @@ B1B5579920EF575B00210D55 /* AuthInputsView.m in Sources */, B1B5597520EFB02A00210D55 /* InviteRecentTableViewCell.m in Sources */, B1B5571E20EE6C4D00210D55 /* ContactDetailsViewController.m in Sources */, + B1D44999243CD73700959523 /* RoomPowerLevel.swift in Sources */, B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */, + B1BEE74823E093260003A4CB /* UserVerificationSessionStatusViewModel.swift in Sources */, B1A6C10723881EF2002882FD /* SlidingModalPresenter.swift in Sources */, B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */, + B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */, B10B3B5B2201DD740072C76B /* KeyBackupBannerCell.swift in Sources */, B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */, B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */, @@ -4689,6 +5228,9 @@ 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */, B1098BFB21ECFE65000DDA48 /* KeyBackupSetupCoordinatorType.swift in Sources */, B1098BF721ECFE65000DDA48 /* PasswordStrength.swift in Sources */, + B1B336BE242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewState.swift in Sources */, + B1BEE73423DF44A60003A4CB /* UserVerificationSessionsStatusViewModelType.swift in Sources */, + B18DEDD9243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewController.swift in Sources */, 324A2052225FC571004FE8B0 /* DeviceVerificationIncomingViewAction.swift in Sources */, B105778D2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift in Sources */, B1B557D820EF5EA900210D55 /* RoomActivitiesView.m in Sources */, @@ -4696,47 +5238,64 @@ B1B5596620EF9E9B00210D55 /* RoomTableViewCell.m in Sources */, B1C45A89232A8C2600165425 /* SettingsIdentityServerViewController.swift in Sources */, B14F143322144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift in Sources */, + B1B336C0242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewModelType.swift in Sources */, 32A6001822C661100042C1D9 /* EditHistoryViewModel.swift in Sources */, + 32607D6E243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift in Sources */, B1B558D020EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1B558CF20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */, + B183226C23F59F810035B2E8 /* CloseButton.swift in Sources */, B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */, + B1CE83BA2422815C00D07506 /* KeyVerificationService.swift in Sources */, B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */, B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */, B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */, B1098BE121ECE09F000DDA48 /* Images.swift in Sources */, + B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */, 3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */, + B1550FCC2420E8F500CE097B /* QRCodeGenerator.swift in Sources */, B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */, 3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */, B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */, + B108932123AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift in Sources */, + B18DEDDA243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewState.swift in Sources */, B1B9DEED22EB34EF0065E677 /* ReactionHistoryCoordinator.swift in Sources */, B1DCC62A22E60D1000625807 /* EmojiMartService.swift in Sources */, B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, + B1B336C5242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewModel.swift in Sources */, B1B558F020EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */, B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */, B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */, + B1BEE74723E093260003A4CB /* UserVerificationSessionStatusViewModelType.swift in Sources */, 3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */, B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */, + B157A7B82445BD86008A5504 /* KeyVerificationScanConfirmationViewModelType.swift in Sources */, B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */, B1B557C620EF5CD400210D55 /* DirectoryServerDetailTableViewCell.m in Sources */, + B1B336C4242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewAction.swift in Sources */, B1B5590920EF768F00210D55 /* RoomEmptyBubbleCell.m in Sources */, 324A2054225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift in Sources */, - 3232ABB92257BE6500AD6A5C /* DeviceVerificationVerifyViewController.swift in Sources */, B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */, B1DCC61822E5E17100625807 /* EmojiPickerViewState.swift in Sources */, + B1B336BF242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewController.swift in Sources */, + B1CE83E32422817200D07506 /* VerifyEmojiCollectionViewCell.swift in Sources */, B1C562DB228C0BB00037F12A /* RoomContextualMenuAction.swift in Sources */, B1B5574720EE6C4D00210D55 /* UsersDevicesViewController.m in Sources */, B1098BFF21ECFE65000DDA48 /* PasswordStrengthView.swift in Sources */, B1B558D220EF768F00210D55 /* RoomEncryptedDataBubbleCell.m in Sources */, B1B558FA20EF768F00210D55 /* RoomMembershipBubbleCell.m in Sources */, B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */, - 3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */, + B1CE83D72422817200D07506 /* KeyVerificationVerifyByScanningViewModelType.swift in Sources */, + 3232ABA1225730E100AD6A5C /* KeyVerificationCoordinatorType.swift in Sources */, B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */, + B1C543B223A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift in Sources */, 323AB947232BD74600C1451F /* AuthFallBackViewController.m in Sources */, B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */, + B1CE83DF2422817200D07506 /* KeyVerificationVerifyBySASCoordinatorType.swift in Sources */, B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */, + B157A7BB2445BD86008A5504 /* KeyVerificationScanConfirmationCoordinatorType.swift in Sources */, B1B5578620EF564900210D55 /* GroupTableViewCellWithSwitch.m in Sources */, B1098BE821ECFE52000DDA48 /* Coordinator.swift in Sources */, B11291EC238D704C0077B478 /* FloatingPoint.swift in Sources */, @@ -4745,13 +5304,17 @@ B1098C0D21ED07E4000DDA48 /* NavigationRouter.swift in Sources */, B110872321F098F0003554A5 /* ActivityIndicatorPresenterType.swift in Sources */, B139C22321FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift in Sources */, + B14084CE23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift in Sources */, B14F143222144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinator.swift in Sources */, B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */, + B1CE83E02422817200D07506 /* KeyVerificationVerifyBySASViewController.swift in Sources */, B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */, B1C3360322F1ED600021BA8D /* MediaPickerCoordinator.swift in Sources */, B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */, B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */, + B12D79FB23E2462200FACEDC /* UserVerificationStartCoordinator.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, + B1B336C3242B933700F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift in Sources */, B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */, B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */, B1B5575C20EE6C4D00210D55 /* DirectoryViewController.m in Sources */, @@ -4766,8 +5329,9 @@ B1B558E520EF768F00210D55 /* RoomMembershipExpandedBubbleCell.m in Sources */, 32BF995121FA29DC00698084 /* SettingsKeyBackupViewModelType.swift in Sources */, B190F55922CE356800AEB493 /* EditHistoryHeaderView.swift in Sources */, - 32F6B96A2270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift in Sources */, + 32F6B96A2270623100BBA352 /* KeyVerificationDataLoadingViewState.swift in Sources */, 32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */, + B18DEDDB243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewAction.swift in Sources */, 32B94DF9228EC26400716A26 /* ReactionsMenuViewAction.swift in Sources */, B1B5599420EFC5E400210D55 /* DecryptionFailureTracker.m in Sources */, F083BDF01E7009ED00A9B29C /* UIViewController+RiotSearch.m in Sources */, @@ -4777,7 +5341,6 @@ 324A2055225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift in Sources */, B1B5572C20EE6C4D00210D55 /* RoomParticipantsViewController.m in Sources */, B1B558EE20EF768F00210D55 /* RoomOutgoingAttachmentBubbleCell.m in Sources */, - 3232ABB52257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift in Sources */, 32BF994F21FA29A400698084 /* SettingsKeyBackupViewModel.swift in Sources */, B190F55D22CE5A9700AEB493 /* EditHistorySection.swift in Sources */, 32DB557E22FDADE50016329E /* ServiceTermsModalScreenViewAction.swift in Sources */, @@ -4786,9 +5349,11 @@ B1B5574E20EE6C4D00210D55 /* DirectoryServerPickerViewController.m in Sources */, B1D211E822C195B400D939BD /* ReactionMenuItemViewData.swift in Sources */, B1DB4F0B223131600065DBFA /* String.swift in Sources */, + B1CE83D82422817200D07506 /* KeyVerificationVerifyByScanningCoordinatorType.swift in Sources */, 3232AB522256558300AD6A5C /* TemplateScreenViewModel.swift in Sources */, B1B5575B20EE6C4D00210D55 /* HomeFilesSearchViewController.m in Sources */, B139C22521FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift in Sources */, + B1BEE71623DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift in Sources */, B1098BFD21ECFE65000DDA48 /* PasswordStrengthManager.swift in Sources */, B1B558F520EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m in Sources */, 3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */, @@ -4796,17 +5361,26 @@ B1B558F820EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1BD71B5238DCBF700BA92E2 /* SlidingModalEmptyViewController.swift in Sources */, B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */, + B18DEDD5243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewModel.swift in Sources */, + B1CE83DA2422817200D07506 /* KeyVerificationVerifyByScanningViewModel.swift in Sources */, 32242F0921E8B05F00725742 /* UIColor.swift in Sources */, B16932E720F3C37100746532 /* HomeMessagesSearchDataSource.m in Sources */, + B12D79FF23E2462200FACEDC /* UserVerificationStartViewState.swift in Sources */, B1B558CE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.m in Sources */, B1B5577D20EE84BF00210D55 /* CircleButton.m in Sources */, + 32607D6D243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift in Sources */, 32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */, B109D6F1222D8C400061B6D9 /* UIApplication.swift in Sources */, + B1BEE73723DF44A60003A4CB /* UserVerificationSessionsStatusViewState.swift in Sources */, + B108932823ABEE6800802670 /* BubbleCellReadReceiptsDisplayable.swift in Sources */, B1B558FF20EF768F00210D55 /* RoomIncomingTextMsgBubbleCell.m in Sources */, B1098C0021ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.swift in Sources */, B1B5591020EF782800210D55 /* TableViewCellWithPhoneNumberTextField.m in Sources */, + B1CE83E52422817200D07506 /* KeyVerificationVerifyBySASViewModel.swift in Sources */, B1DB4F06223015080065DBFA /* Character.swift in Sources */, B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */, + B1C543A4239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift in Sources */, + B1CE83B62422812100D07506 /* KeyVerificationCoordinatorBridgePresenter.swift in Sources */, 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */, B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */, B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */, diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index 1ecb646da..33f2ade6d 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -130,6 +130,17 @@ extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; - (void)logoutSendingRequestServer:(BOOL)sendLogoutServerRequest completion:(void (^)(BOOL isLoggedOut))completion; +/** + Present incoming key verification request to accept. + + @param incomingKeyVerificationRequest The incoming key verification request. + @param The matrix session. + @return Indicate NO if the key verification screen could not be presented. + */ +- (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest + inSession:(MXSession*)session; + +- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession; #pragma mark - Matrix Accounts handling diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index a126f8f7e..bfc43f50c 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -88,7 +88,7 @@ NSString *const AppDelegateDidValidateEmailNotification = @"AppDelegateDidValida NSString *const AppDelegateDidValidateEmailNotificationSIDKey = @"AppDelegateDidValidateEmailNotificationSIDKey"; NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDelegateDidValidateEmailNotificationClientSecretKey"; -@interface AppDelegate () +@interface AppDelegate () { /** Reachability observer @@ -133,14 +133,14 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe RoomKeyRequestViewController *roomKeyRequestViewController; /** - Incoming device verification requests observers + Incoming key verification requests observers */ - id incomingDeviceVerificationObserver; + id incomingKeyVerificationObserver; /** - If any the currently displayed device verification dialog + If any the currently displayed key verification dialog */ - DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter; + KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; /** Account picker used in case of multiple account. @@ -237,6 +237,8 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe @property (weak, nonatomic) UIAlertController *gdprConsentNotGivenAlertController; @property (weak, nonatomic) UIViewController *gdprConsentController; +@property (weak, nonatomic) UIAlertController *incomingKeyVerificationRequestAlertController; + @property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter; @property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter; @@ -251,6 +253,9 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe @property (nonatomic, strong) PKPushRegistry *pushRegistry; @property (nonatomic) NSMutableDictionary *> *incomingPushEventIds; +@property (nonatomic, weak) id userDidSignInOnNewDeviceObserver; +@property (weak, nonatomic) UIAlertController *userNewSignInAlertController; + @end @implementation AppDelegate @@ -264,11 +269,12 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // Set the App Group identifier. MXSDKOptions *sdkOptions = [MXSDKOptions sharedInstance]; sdkOptions.applicationGroupIdentifier = @"group.im.vector"; + sdkOptions.computeE2ERoomSummaryTrust = YES; // Redirect NSLogs to files only if we are not debugging if (!isatty(STDERR_FILENO)) { - [MXLogger redirectNSLogToFiles:YES]; + [MXLogger redirectNSLogToFiles:YES numberOfFiles:50]; } NSLog(@"[AppDelegate] initialize: Done"); @@ -973,18 +979,9 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe - (void)showNotificationAlert:(UIAlertController*)alert { - if (self.window.rootViewController.presentedViewController) - { - [alert popoverPresentationController].sourceView = self.window.rootViewController.presentedViewController.view; - [alert popoverPresentationController].sourceRect = self.window.rootViewController.presentedViewController.view.bounds; - [self.window.rootViewController.presentedViewController presentViewController:alert animated:YES completion:nil]; - } - else - { - [alert popoverPresentationController].sourceView = self.window.rootViewController.view; - [alert popoverPresentationController].sourceRect = self.window.rootViewController.view.bounds; - [self.window.rootViewController presentViewController:alert animated:YES completion:nil]; - } + [alert popoverPresentationController].sourceView = self.presentedViewController.view; + [alert popoverPresentationController].sourceRect = self.presentedViewController.view.bounds; + [self.presentedViewController presentViewController:alert animated:YES completion:nil]; } - (void)onSessionCryptoDidCorruptData:(NSNotification *)notification @@ -1600,132 +1597,6 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe return notificationUserInfo; } -- (void)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPushRule*)rule inAccount:(MXKAccount*)account onComplete:(void (^)(NSString * _Nullable notificationBody))onComplete; -{ - if (!event.content || !event.content.count) - { - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: empty event content"); - onComplete (nil); - return; - } - - MXRoom *room = [account.mxSession roomWithRoomId:event.roomId]; - if (!room) - { - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Unknown room"); - onComplete (nil); - return; - } - - [room state:^(MXRoomState *roomState) { - - NSString *notificationBody; - NSString *eventSenderName = [roomState.members memberName:event.sender]; - - if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) - { - if (room.isMentionsOnly) - { - // A local notification will be displayed only for highlighted notification. - BOOL isHighlighted = NO; - - // Check whether is there an highlight tweak on it - for (MXPushRuleAction *ruleAction in rule.actions) - { - if (ruleAction.actionType == MXPushRuleActionTypeSetTweak) - { - if ([ruleAction.parameters[@"set_tweak"] isEqualToString:@"highlight"]) - { - // Check the highlight tweak "value" - // If not present, highlight. Else check its value before highlighting - if (nil == ruleAction.parameters[@"value"] || YES == [ruleAction.parameters[@"value"] boolValue]) - { - isHighlighted = YES; - break; - } - } - } - } - - if (!isHighlighted) - { - // Ignore this notif. - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Ignore non highlighted notif in mentions only room"); - onComplete(nil); - return; - } - } - - NSString *msgType = event.content[@"msgtype"]; - NSString *content = event.content[@"body"]; - - if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications) - { - // Hide the content - msgType = nil; - } - - NSString *roomDisplayName = room.summary.displayname; - - // Display the room name only if it is different than the sender name - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; - else - // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - } - else - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; - else - // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - } - else if (event.eventType == MXEventTypeCallInvite) - { - NSString *sdp = event.content[@"offer"][@"sdp"]; - BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; - - if (!isVideoCall) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeRoomMember) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeSticker) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - - onComplete(notificationBody); - }]; -} - // iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete:, except with more features - (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule inAccount:(MXKAccount *)account onComplete:(void (^)(UNNotificationContent * _Nullable notificationContent))onComplete; { @@ -1751,6 +1622,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe NSString *threadIdentifier = room.roomId; NSString *eventSenderName = [roomState.members memberName:event.sender]; + NSString *currentUserId = account.mxCredentials.userId; if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) { @@ -1797,6 +1669,9 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe NSString *roomDisplayName = room.summary.displayname; + NSString *myUserId = account.mxSession.myUser.userId; + BOOL isIncomingEvent = ![event.sender isEqualToString:myUserId]; + // Display the room name only if it is different than the sender name if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) { @@ -1814,6 +1689,30 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { notificationBody = [NSString localizedUserNotificationStringForKey:@"IMAGE_FROM_USER" arguments:@[eventSenderName, messageContent]]; } + else if (room.isDirect && isIncomingEvent && [msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + [account.mxSession.crypto.keyVerificationManager keyVerificationFromKeyVerificationEvent:event + success:^(MXKeyVerification * _Nonnull keyVerification) + { + if (keyVerification && keyVerification.state == MXKeyVerificationRequestStatePending) + { + // TODO: Add accept and decline actions to notification + NSString *body = [NSString localizedUserNotificationStringForKey:@"KEY_VERIFICATION_REQUEST_FROM_USER" arguments:@[eventSenderName]]; + + UNNotificationContent *notificationContent = [self notificationContentWithTitle:notificationTitle + body:body + threadIdentifier:threadIdentifier + userId:currentUserId + event:event + pushRule:rule]; + + onComplete(notificationContent); + } + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[AppDelegate][Push] notificationContentForEvent: failed to fetch key verification with error: %@", error); + }]; + } else { // Encrypted messages falls here @@ -1889,27 +1788,47 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe notificationBody = [NSString localizedUserNotificationStringForKey:@"STICKER_FROM_USER" arguments:@[eventSenderName]]; } - UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; - - NSDictionary *notificationUserInfo = [self notificationUserInfoForEvent:event andUserId:account.mxCredentials.userId]; - NSString *notificationSoundName = [self notificationSoundNameFromPushRule:rule]; - NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; - - notificationContent.title = notificationTitle; - notificationContent.body = notificationBody; - notificationContent.threadIdentifier = threadIdentifier; - notificationContent.userInfo = notificationUserInfo; - notificationContent.categoryIdentifier = categoryIdentifier; - - if (notificationSoundName) + if (notificationBody) { - notificationContent.sound = [UNNotificationSound soundNamed:notificationSoundName]; + UNNotificationContent *notificationContent = [self notificationContentWithTitle:notificationTitle + body:notificationBody + threadIdentifier:threadIdentifier + userId:currentUserId + event:event + pushRule:rule]; + + onComplete(notificationContent); } - - onComplete([notificationContent copy]); }]; } +- (UNNotificationContent*)notificationContentWithTitle:(NSString*)title + body:(NSString*)body + threadIdentifier:(NSString*)threadIdentifier + userId:(NSString*)userId + event:(MXEvent*)event + pushRule:(MXPushRule*)pushRule +{ + UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; + + NSDictionary *notificationUserInfo = [self notificationUserInfoForEvent:event andUserId:userId]; + NSString *notificationSoundName = [self notificationSoundNameFromPushRule:pushRule]; + NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; + + notificationContent.title = title; + notificationContent.body = body; + notificationContent.threadIdentifier = threadIdentifier; + notificationContent.userInfo = notificationUserInfo; + notificationContent.categoryIdentifier = categoryIdentifier; + + if (notificationSoundName) + { + notificationContent.sound = [UNNotificationSound soundNamed:notificationSoundName]; + } + + return [notificationContent copy]; +} + /** Display "limited" notifications for events the app was not able to get data (because of /sync failure). @@ -2799,6 +2718,15 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe break; } } + + // Do not warn for unknown devices. We have cross-signing now + mxSession.crypto.warnOnUnknowDevices = NO; + + // Register to user new device sign in notification + [self registerUserDidSignInOnNewDeviceNotificationForSession:mxSession]; + + // Register to new key verification request + [self registerNewRequestNotificationForSession:mxSession]; } else if (mxSession.state == MXSessionStateClosed) { @@ -2858,7 +2786,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { // Check if we need to display a key share dialog [self checkPendingRoomKeyRequests]; - [self checkPendingIncomingDeviceVerificationsInSession:mxSession]; + [self checkPendingIncomingKeyVerificationsInSession:mxSession]; } } @@ -3050,8 +2978,8 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // Enable listening of incoming key share requests [self enableRoomKeyRequestObserver:mxSession]; - // Enable listening of incoming device verification requests - [self enableIncomingDeviceVerificationObserver:mxSession]; + // Enable listening of incoming key verification requests + [self enableIncomingKeyVerificationObserver:mxSession]; } } @@ -3074,8 +3002,8 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // Disable listening of incoming key share requests [self disableRoomKeyRequestObserver:mxSession]; - // Disable listening of incoming device verification requests - [self disableIncomingDeviceVerificationObserver:mxSession]; + // Disable listening of incoming key verification requests + [self disableIncomingKeyVerificationObserver:mxSession]; [mxSessionArray removeObject:mxSession]; @@ -3611,6 +3539,18 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe } } +- (void)presentViewController:(UIViewController*)viewController + animated:(BOOL)animated + completion:(void (^)(void))completion +{ + [self.presentedViewController presentViewController:viewController animated:animated completion:completion]; +} + +- (UIViewController*)presentedViewController +{ + return self.window.rootViewController.presentedViewController ?: self.window.rootViewController; +} + #pragma mark - Matrix Accounts handling - (void)enableInAppNotificationsForAccount:(MXKAccount*)account @@ -3838,38 +3778,46 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { // Create a new room by inviting the other user only if it is defined and not oneself NSArray *invite = ((userId && ![mxSession.myUser.userId isEqualToString:userId]) ? @[userId] : nil); - - [mxSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:invite - invite3PID:nil - isDirect:(invite.count != 0) - preset:kMXRoomPresetTrustedPrivateChat - success:^(MXRoom *room) { - - // Open created room - [self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession]; - - if (completion) - { - completion(); - } - - } - failure:^(NSError *error) { - - NSLog(@"[AppDelegate] Create direct chat failed"); - //Alert user - [self showErrorAsAlert:error]; - - if (completion) - { - completion(); - } - - }]; + + void (^onFailure)(NSError *) = ^(NSError *error){ + NSLog(@"[AppDelegate] Create direct chat failed"); + //Alert user + [self showErrorAsAlert:error]; + + if (completion) + { + completion(); + } + }; + + [mxSession canEnableE2EByDefaultInNewRoomWithUsers:invite success:^(BOOL canEnableE2E) { + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = invite; + roomCreationParameters.isDirect = (invite.count != 0); + roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; + + if (canEnableE2E) + { + roomCreationParameters.initialStateEvents = @[ + [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm + ]]; + } + + [mxSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + // Open created room + [self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession]; + + if (completion) + { + completion(); + } + + } failure:onFailure]; + + } failure:onFailure]; } else if (completion) { @@ -3951,13 +3899,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { [MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested = YES; - UIViewController *viewController = self.window.rootViewController.presentedViewController; - if (!viewController) - { - viewController = self.window.rootViewController; - } - - [MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:viewController completionHandler:^(BOOL granted) { + [MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self.presentedViewController completionHandler:^(BOOL granted) { if (granted) { @@ -4139,14 +4081,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe if (_jitsiViewController) { - if (self.window.rootViewController.presentedViewController) - { - [self.window.rootViewController.presentedViewController presentViewController:_jitsiViewController animated:YES completion:completion]; - } - else - { - [self.window.rootViewController presentViewController:_jitsiViewController animated:YES completion:completion]; - } + [self presentViewController:_jitsiViewController animated:YES completion:completion]; } } @@ -4288,10 +4223,8 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe }]; }; - UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; - [self.slidingModalPresenter present:widgetPermissionViewController - from:presentingViewController + from:self.presentedViewController animated:YES completion:nil]; } @@ -4389,14 +4322,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe if (currentCallViewController) { - if (self.window.rootViewController.presentedViewController) - { - [self.window.rootViewController.presentedViewController presentViewController:currentCallViewController animated:animated completion:completion]; - } - else - { - [self.window.rootViewController presentViewController:currentCallViewController animated:animated completion:completion]; - } + [self presentViewController:currentCallViewController animated:animated completion:completion]; } } @@ -4705,12 +4631,12 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe NSString *deviceId = [pendingKeyRequests deviceIdsForUser:userId].firstObject; // Give the client a chance to refresh the device list - [mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { + [mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:deviceId forUser:userId]; if (deviceInfo) { - BOOL wasNewDevice = (deviceInfo.verified == MXDeviceUnknown); + BOOL wasNewDevice = (deviceInfo.trustLevel.localVerificationStatus == MXDeviceUnknown); void (^openDialog)(void) = ^void() { @@ -4766,54 +4692,54 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe } } -#pragma mark - Incoming device verification requests handling +#pragma mark - Incoming key verification handling -- (void)enableIncomingDeviceVerificationObserver:(MXSession*)mxSession +- (void)enableIncomingKeyVerificationObserver:(MXSession*)mxSession { - incomingDeviceVerificationObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:MXDeviceVerificationManagerNewTransactionNotification - object:mxSession.crypto.deviceVerificationManager + incomingKeyVerificationObserver = + [[NSNotificationCenter defaultCenter] addObserverForName:MXKeyVerificationManagerNewTransactionNotification + object:mxSession.crypto.keyVerificationManager queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - NSObject *object = notif.userInfo[MXDeviceVerificationManagerNotificationTransactionKey]; + NSObject *object = notif.userInfo[MXKeyVerificationManagerNotificationTransactionKey]; if ([object isKindOfClass:MXIncomingSASTransaction.class]) { - [self checkPendingIncomingDeviceVerificationsInSession:mxSession]; + [self checkPendingIncomingKeyVerificationsInSession:mxSession]; } }]; } -- (void)disableIncomingDeviceVerificationObserver:(MXSession*)mxSession +- (void)disableIncomingKeyVerificationObserver:(MXSession*)mxSession { - if (incomingDeviceVerificationObserver) + if (incomingKeyVerificationObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:incomingDeviceVerificationObserver]; - incomingDeviceVerificationObserver = nil; + [[NSNotificationCenter defaultCenter] removeObserver:incomingKeyVerificationObserver]; + incomingKeyVerificationObserver = nil; } } -// Check if an incoming device verification dialog must be displayed for the given session -- (void)checkPendingIncomingDeviceVerificationsInSession:(MXSession*)mxSession +// Check if an incoming key verification dialog must be displayed for the given session +- (void)checkPendingIncomingKeyVerificationsInSession:(MXSession*)mxSession { if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { - NSLog(@"[AppDelegate][MXKeyVerification] checkPendingIncomingDeviceVerificationsInSession: called while the app is not active. Ignore it."); + NSLog(@"[AppDelegate][MXKeyVerification] checkPendingIncomingKeyVerificationsInSession: called while the app is not active. Ignore it."); return; } - [mxSession.crypto.deviceVerificationManager transactions:^(NSArray * _Nonnull transactions) { + [mxSession.crypto.keyVerificationManager transactions:^(NSArray * _Nonnull transactions) { - NSLog(@"[AppDelegate][MXKeyVerification] checkPendingIncomingDeviceVerificationsInSession: transactions: %@", transactions); + NSLog(@"[AppDelegate][MXKeyVerification] checkPendingIncomingKeyVerificationsInSession: transactions: %@", transactions); - for (MXDeviceVerificationTransaction *transaction in transactions) + for (MXKeyVerificationTransaction *transaction in transactions) { if (transaction.isIncoming) { MXIncomingSASTransaction *incomingTransaction = (MXIncomingSASTransaction*)transaction; if (incomingTransaction.state == MXSASTransactionStateIncomingShowAccept) { - [self presentIncomingDeviceVerification:incomingTransaction inSession:mxSession]; + [self presentIncomingKeyVerification:incomingTransaction inSession:mxSession]; break; } } @@ -4821,45 +4747,333 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe }]; } -// Check all opened MXSessions for incoming device verification dialog -- (void)checkPendingIncomingDeviceVerifications +// Check all opened MXSessions for incoming key verification dialog +- (void)checkPendingIncomingKeyVerifications { for (MXSession *mxSession in mxSessionArray) { - [self checkPendingIncomingDeviceVerificationsInSession:mxSession]; + [self checkPendingIncomingKeyVerificationsInSession:mxSession]; } } -- (BOOL)presentIncomingDeviceVerification:(MXIncomingSASTransaction*)transaction inSession:(MXSession*)mxSession +- (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest + inSession:(MXSession*)session { - NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingDeviceVerification: %@", transaction); + BOOL presented = NO; + + if (!keyVerificationCoordinatorBridgePresenter) + { + NSLog(@"[AppDelegate] presentIncomingKeyVerificationRequest"); + + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:session]; + keyVerificationCoordinatorBridgePresenter.delegate = self; + + [keyVerificationCoordinatorBridgePresenter presentFrom:self.presentedViewController incomingKeyVerificationRequest:incomingKeyVerificationRequest animated:YES]; + + presented = YES; + } + else + { + NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingKeyVerificationRequest: Controller already presented."); + } + + return presented; +} + +- (BOOL)presentIncomingKeyVerification:(MXIncomingSASTransaction*)transaction inSession:(MXSession*)mxSession +{ + NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingKeyVerification: %@", transaction); BOOL presented = NO; - if (!deviceVerificationCoordinatorBridgePresenter) + if (!keyVerificationCoordinatorBridgePresenter) { - UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; - deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession]; - deviceVerificationCoordinatorBridgePresenter.delegate = self; - - [deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController incomingTransaction:transaction animated:YES]; + [keyVerificationCoordinatorBridgePresenter presentFrom:self.presentedViewController incomingTransaction:transaction animated:YES]; presented = YES; } else { - NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingDeviceVerification: Controller already presented."); + NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingKeyVerification: Controller already presented."); } return presented; } -- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession { - [deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{ - [self checkPendingIncomingDeviceVerifications]; + NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: %@", roomMember); + + BOOL presented = NO; + if (!keyVerificationCoordinatorBridgePresenter) + { + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; + + [keyVerificationCoordinatorBridgePresenter presentFrom:self.presentedViewController roomMember:roomMember animated:YES]; + + presented = YES; + } + else + { + NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: Controller already presented."); + } + return presented; +} + +- (BOOL)presentSelfVerificationForOtherDeviceId:(NSString*)deviceId inSession:(MXSession*)mxSession +{ + NSLog(@"[AppDelegate][MXKeyVerification] presentSelfVerificationForOtherDeviceId: %@", deviceId); + + BOOL presented = NO; + if (!keyVerificationCoordinatorBridgePresenter) + { + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; + + [keyVerificationCoordinatorBridgePresenter presentFrom:self.presentedViewController otherUserId:mxSession.myUser.userId otherDeviceId:deviceId animated:YES]; + + presented = YES; + } + else + { + NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: Controller already presented."); + } + return presented; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ + [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{ + [self checkPendingIncomingKeyVerifications]; }]; - deviceVerificationCoordinatorBridgePresenter = nil; + keyVerificationCoordinatorBridgePresenter = nil; +} + +#pragma mark - New request + +- (void)registerNewRequestNotificationForSession:(MXSession*)session +{ + MXKeyVerificationManager *keyverificationManager = session.crypto.keyVerificationManager; + + if (!keyverificationManager) + { + return; + } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyVerificationNewRequestNotification:) name:MXKeyVerificationManagerNewRequestNotification object:keyverificationManager]; +} + +- (void)keyVerificationNewRequestNotification:(NSNotification *)notification +{ + if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) + { + return; + } + + NSDictionary *userInfo = notification.userInfo; + + MXKeyVerificationRequest *keyVerificationRequest = userInfo[MXKeyVerificationManagerNotificationRequestKey]; + + if ([keyVerificationRequest isKindOfClass:MXKeyVerificationByDMRequest.class]) + { + MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)keyVerificationRequest; + + if (!keyVerificationByDMRequest.isFromMyUser && keyVerificationByDMRequest.state == MXKeyVerificationRequestStatePending) + { + MXKAccount *currentAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; + MXSession *session = currentAccount.mxSession; + MXRoom *room = [currentAccount.mxSession roomWithRoomId:keyVerificationByDMRequest.roomId]; + if (!room) + { + NSLog(@"[AppDelegate][KeyVerification] keyVerificationRequestDidChangeNotification: Unknown room"); + return; + } + + NSString *sender = keyVerificationByDMRequest.otherUser; + + [room state:^(MXRoomState *roomState) { + + NSString *senderName = [roomState.members memberName:sender]; + + [self presentNewKeyVerificationRequestAlertForSession:session senderName:senderName senderId:sender request:keyVerificationByDMRequest]; + }]; + } + } + else if ([keyVerificationRequest isKindOfClass:MXKeyVerificationByToDeviceRequest.class]) + { + MXKeyVerificationByToDeviceRequest *keyVerificationByToDeviceRequest = (MXKeyVerificationByToDeviceRequest*)keyVerificationRequest; + + // We support only self verification + if (keyVerificationByToDeviceRequest.isFromMyUser + && !keyVerificationByToDeviceRequest.isFromMyDevice + && keyVerificationByToDeviceRequest.state == MXKeyVerificationRequestStatePending) + { + NSString *myUserId = keyVerificationByToDeviceRequest.otherUser; + MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:myUserId]; + if (account) + { + MXSession *session = account.mxSession; + MXUser *user = [session userWithUserId:myUserId]; + + [self presentNewKeyVerificationRequestAlertForSession:session senderName:user.displayname senderId:user.userId request:keyVerificationRequest]; + } + } + } +} + +- (void)presentNewKeyVerificationRequestAlertForSession:(MXSession*)session + senderName:(NSString*)senderName + senderId:(NSString*)senderId + request:(MXKeyVerificationRequest*)keyVerificationRequest +{ + if (self.incomingKeyVerificationRequestAlertController) + { + [self.incomingKeyVerificationRequestAlertController dismissViewControllerAnimated:NO completion:nil]; + } + + NSString *senderInfo; + + if (senderName) + { + senderInfo = [NSString stringWithFormat:@"%@ (%@)", senderName, senderId]; + } + else + { + senderInfo = senderId; + } + + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_title", @"Vector", nil) + message:senderInfo + preferredStyle:UIAlertControllerStyleAlert]; + + [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_approval_accept", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + [self presentIncomingKeyVerificationRequest:keyVerificationRequest inSession:session]; + }]]; + + [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_approval_decline", @"Vector", nil) + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) + { + [keyVerificationRequest cancelWithCancelCode:MXTransactionCancelCode.user success:^{ + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[AppDelegate][KeyVerification] Fail to cancel incoming key verification request with error: %@", error); + }]; + }]]; + + [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"later", @"Vector", nil) + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) + { + }]]; + + [self presentViewController:alertController animated:YES completion:nil]; + + self.incomingKeyVerificationRequestAlertController = alertController; +} + +#pragma mark - New Sign In + +- (void)registerUserDidSignInOnNewDeviceNotificationForSession:(MXSession*)session +{ + MXCrossSigning *crossSigning = session.crypto.crossSigning; + + if (!crossSigning) + { + return; + } + + self.userDidSignInOnNewDeviceObserver = [NSNotificationCenter.defaultCenter addObserverForName:MXCrossSigningMyUserDidSignInOnNewDeviceNotification + object:crossSigning + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notification) + { + NSArray *deviceIds = notification.userInfo[MXCrossSigningNotificationDeviceIdsKey]; + + [session.matrixRestClient devices:^(NSArray *devices) { + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.deviceId IN %@", deviceIds]; + NSArray *newDevices = [devices filteredArrayUsingPredicate:predicate]; + + NSArray *sortedDevices = [newDevices sortedArrayUsingComparator:^NSComparisonResult(MXDevice * _Nonnull device1, MXDevice * _Nonnull device2) { + + if (device1.lastSeenTs == device2.lastSeenTs) + { + return NSOrderedSame; + } + + return device1.lastSeenTs > device2.lastSeenTs ? NSOrderedDescending : NSOrderedAscending; + }]; + + MXDevice *mostRecentDevice = sortedDevices.lastObject; + + if (mostRecentDevice) + { + [self presentNewSignInAlertForDevice:mostRecentDevice inSession:session]; + } + + } failure:^(NSError *error) { + NSLog(@"[AppDelegate][NewSignIn] Fail to fetch devices"); + }]; + }]; +} + +- (void)presentNewSignInAlertForDevice:(MXDevice*)device inSession:(MXSession*)session +{ + if (self.userNewSignInAlertController) + { + return; + } + + NSString *deviceInfo; + + if (device.displayName) + { + deviceInfo = [NSString stringWithFormat:@"%@ (%@)", device.displayName, device.deviceId]; + } + else + { + deviceInfo = device.deviceId; + } + + NSString *alertMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"device_verification_self_verify_alert_message", @"Vector", nil), deviceInfo]; + + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"device_verification_self_verify_alert_title", @"Vector", nil) + message:alertMessage + preferredStyle:UIAlertControllerStyleAlert]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"continue", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + [self presentSelfVerificationForOtherDeviceId:device.deviceId inSession:session]; + }]]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"device_verification_self_verify_alert_cancel_action", @"Vector", nil) + style:UIAlertActionStyleCancel + handler:nil]]; + + [self presentViewController:alert animated:YES completion:nil]; + + self.userNewSignInAlertController = alert; } #pragma mark - GDPR consent @@ -4880,8 +5094,6 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe self.gdprConsentNotGivenAlertController = nil; self.gdprConsentController = nil; - UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; - __weak typeof(self) weakSelf = self; MXSession *mainSession = self.mxSessions.firstObject; @@ -4901,7 +5113,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe if (strongSelf) { - [strongSelf presentGDPRConsentFromViewController:presentingViewController consentURI:consentURI]; + [strongSelf presentGDPRConsentFromViewController:self.presentedViewController consentURI:consentURI]; } }]]; @@ -4909,7 +5121,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe style:UIAlertActionStyleCancel handler:nil]]; - [presentingViewController presentViewController:alert animated:YES completion:nil]; + [self presentViewController:alert animated:YES completion:nil]; self.gdprConsentNotGivenAlertController = alert; } @@ -5005,9 +5217,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe serviceTermsModalCoordinatorBridgePresenter.delegate = self; - UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; - - [serviceTermsModalCoordinatorBridgePresenter presentFrom:presentingViewController animated:YES]; + [serviceTermsModalCoordinatorBridgePresenter presentFrom:self.presentedViewController animated:YES]; self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter; } diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json new file mode 100644 index 000000000..d21aeca7b --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "camera.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "camera@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "camera@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/camera.png b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera.png new file mode 100644 index 000000000..541f81921 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera.png differ diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@2x.png b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@2x.png new file mode 100644 index 000000000..af9e1c0ec Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@3x.png b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@3x.png new file mode 100644 index 000000000..3458fb8d8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/Contents.json b/Riot/Assets/Images.xcassets/Encryption/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json new file mode 100644 index 000000000..91448ccb6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "encryption_normal.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "encryption_normal@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "encryption_normal@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png new file mode 100644 index 000000000..c628f322a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png new file mode 100644 index 000000000..07555f27b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png new file mode 100644 index 000000000..85e974954 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json new file mode 100644 index 000000000..cfcf9df66 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "encryption_trusted.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "encryption_trusted@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "encryption_trusted@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png new file mode 100644 index 000000000..748d42a77 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png new file mode 100644 index 000000000..02488c979 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png new file mode 100644 index 000000000..9154725c9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json new file mode 100644 index 000000000..d71cc04ce --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "encryption_warning.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "encryption_warning@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "encryption_warning@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png new file mode 100644 index 000000000..099c1c2a5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png new file mode 100644 index 000000000..15bd69270 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png new file mode 100644 index 000000000..2629a149a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png differ diff --git a/Riot/Assets/Images.xcassets/KeyVerification/Contents.json b/Riot/Assets/Images.xcassets/KeyVerification/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/Riot/Assets/Images.xcassets/KeyVerification/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/Contents.json b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/Contents.json new file mode 100644 index 000000000..6673d601f --- /dev/null +++ b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "verification_success_shield.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "verification_success_shield@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "verification_success_shield@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield.png b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield.png new file mode 100644 index 000000000..f217c1d9b Binary files /dev/null and b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield.png differ diff --git a/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield@2x.png b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield@2x.png new file mode 100644 index 000000000..a8a75419a Binary files /dev/null and b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield@2x.png differ diff --git a/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield@3x.png b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield@3x.png new file mode 100644 index 000000000..d8c507595 Binary files /dev/null and b/Riot/Assets/Images.xcassets/KeyVerification/key_verification_success_shield.imageset/verification_success_shield@3x.png differ diff --git a/Riot/Assets/Riot-Defaults.plist b/Riot/Assets/Riot-Defaults.plist index 5c360dd63..d79cce7dd 100644 --- a/Riot/Assets/Riot-Defaults.plist +++ b/Riot/Assets/Riot-Defaults.plist @@ -43,7 +43,7 @@ url https://piwik.riot.im/piwik.php siteId - 1 + 14 showAllEventsInRoomHistory diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 9246d1c2c..936014d80 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -923,3 +923,20 @@ "widget_menu_revoke_permission" = "Премахни достъпа за мен"; "widget_menu_remove" = "Премахни за всички"; "widget_integration_manager_disabled" = "Необходимо е да включите мениджър на интеграции от настройки"; +// Accessibility +"accessibility_checkbox_label" = "отметка"; +"settings_labs_dm_key_verification" = "Потвърждение на ключ чрез директно съобщение"; +"settings_labs_cross_signing" = "Кръстосано-подписване"; +"widget_picker_manage_integrations" = "Управление на интеграциите..."; +// Room widget permissions +"room_widget_permission_title" = "Зареждане на приспособление"; +"room_widget_permission_creator_info_title" = "Приспособлението беше добавено от:"; +"room_widget_permission_webview_information_title" = "Използването му може да сложи бисквитки и да сподели данни с %@:\n"; +"room_widget_permission_information_title" = "Използването му може да сподели данни с %@:\n"; +"room_widget_permission_display_name_permission" = "Вашето име"; +"room_widget_permission_avatar_url_permission" = "Адреса на вашата снимка"; +"room_widget_permission_user_id_permission" = "Вашето потребителското ID"; +"room_widget_permission_theme_permission" = "Вашата тема"; +"room_widget_permission_widget_id_permission" = "ID на приспособление"; +"room_widget_permission_room_id_permission" = "ID на стая"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Отметнете за да приемете %@"; diff --git a/Riot/Assets/cy.lproj/Vector.strings b/Riot/Assets/cy.lproj/Vector.strings index 2ef812cdb..e3fdff348 100644 --- a/Riot/Assets/cy.lproj/Vector.strings +++ b/Riot/Assets/cy.lproj/Vector.strings @@ -92,7 +92,7 @@ "auth_reset_password_email_validation_message" = "Mae e-bost wedi'i anfon at %@. Ar ôl i chi ddilyn y ddolen sydd ynddo, cliciwch isod."; "auth_reset_password_next_step_button" = "Rwyf wedi gwirio fy nghyfeiriad e-bost"; "auth_reset_password_error_unauthorized" = "Methiant gwirio cyfeiriad e-bost: gwnewch yn siŵr eich bod wedi clicio'r ddolen yn yr e-bost"; -"auth_reset_password_error_not_found" = "Nid yw'n ymddangos bod eich cyfeiriad e-bost yn gysylltiedig ag Matrix ID ar y hafanweinydd hwn."; +"auth_reset_password_error_not_found" = "Nid yw'n ymddangos bod eich cyfeiriad e-bost yn gysylltiedig ag Dynodwr Matrix ar y hafanweinydd hwn."; "auth_reset_password_error_is_required" = "Nid oes unrhyw weinydd adnabod wedi'i osod: ychwanegwch un yn opsiynau'r gweinydd i ailosod eich cyfrinair."; "auth_reset_password_success_message" = "Mae eich cyfrinair wedi'i ailosod.\n\nRydych wedi cael eich allgofnodi o bob dyfais ac ni fyddwch yn derbyn hysbysiadau mwyach. I ail-alluogi hysbysiadau, ail-fewngofnodwch ar bob dyfais."; "auth_add_email_and_phone_warning" = "Ni chefnogir cofrestru gydag e-bost a rhif ffôn ar yr un pryd nes bod yr api yn bodoli. Dim ond y rhif ffôn fydd yn cael ei ystyried. Gallwch ychwanegu eich e-bost at eich proffil mewn gosodiadau."; @@ -126,7 +126,7 @@ "room_creation_keep_private" = "Cadw'n breifat"; "room_creation_make_private" = "Gwneud yn breifat"; "room_creation_wait_for_creation" = "Mae ystafell eisoes yn cael ei greu. Arhoswch os gwelwch yn dda."; -"room_creation_invite_another_user" = "Chwilio / gwahodd yn ôl ID defnyddiwr, Enw, new e-bost"; +"room_creation_invite_another_user" = "Chwilio / gwahodd yn ôl Dynodwr Defnyddiwr, Enw, new e-bost"; "room_creation_error_invite_user_by_email_without_identity_server" = "Nid oes unrhyw weinydd adnabod wedi'i osod felly ni allwch ychwanegu cyfranogwr gydag e-bost."; // Room recents "room_recents_directory_section" = "CYFEIRIADUR YSTAFELLOEDD"; @@ -158,7 +158,7 @@ "search_people" = "Pobl"; "search_files" = "Ffeiliau"; "search_default_placeholder" = "Chwilio"; -"search_people_placeholder" = "Chwilio yn ôl ID defnyddiwr, Enw, new e-bost"; +"search_people_placeholder" = "Chwilio yn ôl Dynodwr Defnyddiwr, Enw, new e-bost"; "search_no_result" = "Dim canlyniadau"; "search_in_progress" = "Chwilio…"; // Directory @@ -191,9 +191,9 @@ "room_participants_invite_prompt_title" = "Cadarnhad"; "room_participants_invite_prompt_msg" = "Ydych chi'n siwr eich bod eisau ychwanegu %@ i'r sgwrs?"; "room_participants_filter_room_members" = "Hidlo aelodau'r ystafell"; -"room_participants_invite_another_user" = "Chwilio / gwahodd yn ôl ID defnyddiwr, Enw, new e-bost"; +"room_participants_invite_another_user" = "Chwilio / gwahodd yn ôl Dynodwr Defnyddiwr, Enw, new e-bost"; "room_participants_invite_malformed_id_title" = "Gwall gwahoddiad"; -"room_participants_invite_malformed_id" = "ID camffurfiedig. Dylai fod yn gyfeiriad e-bost neu'n ID Matrix fel '@localpart:domain'"; +"room_participants_invite_malformed_id" = "Dynodwr camffurfiedig. Dylai fod yn gyfeiriad e-bost neu'n Ddynodwr Matrix fel '@localpart:domain'"; "room_participants_invited_section" = "GWAHODDWYD"; "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Nid oes unrhyw weinydd adnabod wedi'i osod felly ni allwch ddechrau sgwrs gyda chyswllt gan ddefnyddio e-bost."; "room_participants_online" = "Ar-lein"; @@ -417,7 +417,7 @@ "settings_add_3pid_password_message" = "I barhau, rhowch eich cyfrinair os gwelwch yn dda"; "settings_add_3pid_invalid_password_message" = "Cyfrinair annilys"; "settings_crypto_device_name" = "Enw Cyhoeddus y Ddyfais: "; -"settings_crypto_device_id" = "\nID y ddyfais: "; +"settings_crypto_device_id" = "\nDynodwr y ddyfais: "; "settings_crypto_device_key" = "\nAllwedd y ddyfais:\n"; "settings_crypto_export" = "Allfudo allweddi"; "settings_crypto_blacklist_unverified_devices" = "Amgryptio i ddyfeisiau wedi'u gwirio yn unig"; @@ -425,14 +425,14 @@ "settings_key_backup_info" = "Sicrheir negeseuon wedi'u hamgryptio gydag amgryptio o'r dechrau i'r diwedd. Dim ond chi a'r derbynnydd / derbynwyr sydd â'r allweddi i ddarllen y negeseuon hyn."; "settings_key_backup_info_checking" = "Gwirio..."; "settings_key_backup_info_none" = "Nid yw'ch allweddi yn cael eu cadw wrth gefn o'r ddyfais hon."; -"settings_key_backup_info_signout_warning" = "Cysylltwch y ddyfais hon â Allweddi Wrth Gefn cyn arwyddo allan er mwyn osgoi colli unrhyw allweddi a allai fod ar y ddyfais hon yn unig."; +"settings_key_backup_info_signout_warning" = "Cysylltwch y ddyfais hon â Allweddi Wrth Gefn cyn allgofnodi er mwyn osgoi colli unrhyw allweddi a allai fod ar y ddyfais hon yn unig."; "settings_key_backup_info_version" = "Fersiwn Allweddi Wrth Gefn: %@"; "settings_key_backup_info_algorithm" = "Algorithm: %@"; "settings_key_backup_info_valid" = "Mae'r ddyfais hon yn gwneud copi wrth gefn o'ch allweddi."; "settings_key_backup_info_not_valid" = "Nid yw'r ddyfais hon yn gwneud copi wrth gefn o'ch allweddi, ond mae gennych gopi wrth gefn y gallwch ei adfer ac ychwanegu ato wrth symud ymlaen."; "settings_key_backup_info_progress" = "Creu copi wrth gefn o allweddi %@..."; "settings_key_backup_info_progress_done" = "Pob allwedd â copi wrth gefn"; -"settings_key_backup_info_trust_signature_unknown" = "Mae gan Allweddi Wrth Gefn lofnod o'r ddyfais gydag ID: %@"; +"settings_key_backup_info_trust_signature_unknown" = "Mae gan Allweddi Wrth Gefn lofnod o'r ddyfais gyda dynodwr: %@"; "settings_key_backup_info_trust_signature_valid" = "Mae gan Allweddi Wrth Gefn lofnod dilys o'r ddyfais hon"; "settings_key_backup_info_trust_signature_valid_device_verified" = "Mae gan Allweddi Wrth Gefn lofnod dilys o %@"; "settings_key_backup_info_trust_signature_valid_device_unverified" = "Mae gan Allweddi Wrth Gefn lofnod o %@"; @@ -516,12 +516,12 @@ "room_details_addresses_disable_main_address_prompt_title" = "Rhybydd prif gyfeiriad"; "room_details_addresses_disable_main_address_prompt_msg" = "Ni fydd gennych brif gyfeiriad wedi'i nodi. Dewisir ar hap prif gyfeiriad diofyn ar gyfer yr ystafell hon"; "room_details_flair_section" = "Dangos dawn i gymunedau"; -"room_details_new_flair_placeholder" = "Ychwanegu ID cymunedol newydd (e.e. +foo%@)"; +"room_details_new_flair_placeholder" = "Ychwanegu dynodwr cymunedol newydd (e.e. +foo%@)"; "room_details_flair_invalid_id_prompt_title" = "Fformat annilys"; "room_details_flair_invalid_id_prompt_msg" = "Nid yw %@ yn ddynodwr dilys ar gyfer cymuned"; "room_details_banned_users_section" = "Defnyddwyr gwaharddedig"; "room_details_advanced_section" = "Uwch"; -"room_details_advanced_room_id" = "ID Ystafell:"; +"room_details_advanced_room_id" = "Dynodwr Ystafell:"; "room_details_advanced_enable_e2e_encryption" = "Galluogi amgryptio (rhybudd: ni ellir ei anablu eto!)"; "room_details_advanced_e2e_encryption_enabled" = "Mae amgryptio wedi'i alluogi yn yr ystafell hon"; "room_details_advanced_e2e_encryption_disabled" = "Nid yw amgryptio wedi'i alluogi yn yr ystafell hon."; @@ -542,7 +542,7 @@ "room_details_save_changes_prompt" = "Hoffech chi gadw'r newidiadau?"; "room_details_set_main_address" = "Gosod fel Prif Gyfeiriad"; "room_details_unset_main_address" = "Dad-osod fel Prif Gyfeiriad"; -"room_details_copy_room_id" = "Copio ID Ystafell"; +"room_details_copy_room_id" = "Copio Dynodwr Ystafell"; "room_details_copy_room_address" = "Copio Cyfeiriad Ystafell"; "room_details_copy_room_url" = "Copio URL Ystafell"; // Group Details @@ -565,9 +565,9 @@ "group_participants_invite_prompt_title" = "Cadarnhad"; "group_participants_invite_prompt_msg" = "Ydych chi'n siwr eich bod eisau gwahodd %@ i'r grŵp?"; "group_participants_filter_members" = "Hidlo aelodau'r cymuned"; -"group_participants_invite_another_user" = "Chwilio / gwahodd yn ôl ID Defnyddiwr neu Enw"; +"group_participants_invite_another_user" = "Chwilio / gwahodd yn ôl Dynodwr Defnyddiwr neu Enw"; "group_participants_invite_malformed_id_title" = "Gwall gwahoddiad"; -"group_participants_invite_malformed_id" = "ID camffurfiedig. Dylai fod yn ID Matrix fel '@localpart:domain'"; +"group_participants_invite_malformed_id" = "Dynodwr camffurfiedig. Dylai fod yn Ddynodwr Matrix fel '@localpart:domain'"; "group_participants_invited_section" = "GWAHODDWYD"; // Group rooms "group_rooms_filter_rooms" = "Hidlo ystafelloedd y gymuned"; @@ -693,7 +693,7 @@ "service_terms_modal_title_identity_server" = "Darganfod Cysylltiadau"; "service_terms_modal_message_identity_server" = "Derbyn telerau'r gweinydd adnabod (%@) i ddarganfod cysylltiadau."; "deactivate_account_title" = "Dad-actifadu Cyfrif"; -"deactivate_account_informations_part1" = "Bydd hyn yn golygu na ellir defnyddio'ch cyfrif yn barhaol. Ni fyddwch yn gallu mewngofnodi, ac ni fydd unrhyw un yn gallu ailgofrestru'r un ID defnyddiwr. Bydd hyn yn achosi i'ch cyfrif adael yr holl ystafelloedd y mae'n cymryd rhan ynddynt, a bydd yn tynnu manylion eich cyfrif o'ch gweinydd adnabod. "; +"deactivate_account_informations_part1" = "Bydd hyn yn golygu na ellir defnyddio'ch cyfrif yn barhaol. Ni fyddwch yn gallu mewngofnodi, ac ni fydd unrhyw un yn gallu ailgofrestru'r un dynodwr defnyddiwr. Bydd hyn yn achosi i'ch cyfrif adael yr holl ystafelloedd y mae'n cymryd rhan ynddynt, a bydd yn tynnu manylion eich cyfrif o'ch gweinydd adnabod. "; "deactivate_account_informations_part2_emphasize" = "Ni ellir gwrthdroi'r weithred hon."; "deactivate_account_informations_part3" = "\n\nYn dad-actifadu eich cyfrif "; "deactivate_account_informations_part4_emphasize" = "nid yw yn ddiofyn yn achosi inni anghofio negeseuon yr ydych wedi'u hanfon. "; @@ -883,3 +883,28 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Ychwanegwch weinydd adnabod yn eich gosodiadau i wahodd trwy e-bost."; "error_not_supported_on_mobile" = "Ni allwch wneud hyn o %@ ffôn symudol."; +// Accessibility +"accessibility_checkbox_label" = "blwch ticio"; +"settings_integrations" = "INTEGREIDDIADAU"; +"settings_integrations_allow_button" = "Rheoli integreiddiadau"; +"settings_integrations_allow_description" = "Defnyddiwch Reolwr Integreiddio (%@) i reoli bots, pontydd, teclynnau a phecynnau sticeri.\n\nMae Rheolwyr Integreiddio yn derbyn data cyfluniad, a gallant addasu teclynnau, anfon gwahoddiadau ystafell a gosod lefelau pŵer ar eich rhan."; +"settings_labs_dm_key_verification" = "Gwirio allweddi trwy neges uniongyrchol"; +"settings_labs_cross_signing" = "Traws-arwyddo"; +"widget_menu_refresh" = "Adnewyddu"; +"widget_menu_open_outside" = "Agor mewn porwr"; +"widget_menu_revoke_permission" = "Dirymu mynediad i mi"; +"widget_menu_remove" = "Gwaredu i bawb"; +"widget_integration_manager_disabled" = "Mae angen i chi alluogi Rheolwr Integreiddio yn y gosodiadau"; +"widget_picker_manage_integrations" = "Rheoli integreiddiadau..."; +// Room widget permissions +"room_widget_permission_title" = "Llwytho Teclyn"; +"room_widget_permission_creator_info_title" = "Ychwanegwyd y teclyn hwn gan:"; +"room_widget_permission_webview_information_title" = "Gall ei ddefnyddio osod cwcis a rhannu data gyda %@:\n"; +"room_widget_permission_information_title" = "Gall ei ddefnyddio rhannu data gyda %@:\n"; +"room_widget_permission_display_name_permission" = "Eich enw arddangos"; +"room_widget_permission_avatar_url_permission" = "Eich URL dangoslun"; +"room_widget_permission_user_id_permission" = "Eich dynodwr defnyddiwr"; +"room_widget_permission_theme_permission" = "Eich thema"; +"room_widget_permission_widget_id_permission" = "Dynodwr Teclyn"; +"room_widget_permission_room_id_permission" = "Dynodwr Ystafell"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Ticiwch i dderbyn %@"; diff --git a/Riot/Assets/de.lproj/Localizable.strings b/Riot/Assets/de.lproj/Localizable.strings index bcadaeb5f..3c8e1644a 100644 --- a/Riot/Assets/de.lproj/Localizable.strings +++ b/Riot/Assets/de.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ hat einen Sticker gesendet"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ möchte verifizieren"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 3f9f78ddf..4031f43d3 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -904,3 +904,5 @@ "widget_menu_revoke_permission" = "Zugriff für mich widerrufen"; "widget_menu_remove" = "Für alle entfernen"; "widget_integration_manager_disabled" = "Sie müssen den Integration Manager in den Einstellungen aktivieren"; +"settings_discovery_settings" = "ERKENNUNG"; +"settings_three_pids_management_information_part2" = "Erkennung"; diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index fc5cc015a..61d8a3e98 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -109,3 +109,7 @@ /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'"; + +/** Key verification **/ + +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ wants to verify"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0117d6693..63803aa33 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -57,6 +57,7 @@ "send_to" = "Send to %@"; "sending" = "Sending"; "close" = "Close"; +"skip" = "Skip"; // Accessibility "accessibility_checkbox_label" = "checkbox"; @@ -117,7 +118,7 @@ "auth_reset_password_error_unauthorized" = "Failed to verify email address: make sure you clicked the link in the email"; "auth_reset_password_error_not_found" = "Your email address does not appear to be associated with a Matrix ID on this homeserver."; "auth_reset_password_error_is_required" = "No identity server is configured: add one in server options to reset your password."; -"auth_reset_password_success_message" = "Your password has been reset.\n\nYou have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, re-log in on each device."; +"auth_reset_password_success_message" = "Your password has been reset.\n\nYou have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, re-log in on each device."; "auth_add_email_and_phone_warning" = "Registration with email and phone number at once is not supported yet until the api exists. Only the phone number will be taken into account. You may add your email to your profile in settings."; "auth_accept_policies" = "Please review and accept the policies of this homeserver:"; "auth_autodiscover_invalid_response" = "Invalid homeserver discovery response"; @@ -239,8 +240,9 @@ "room_participants_action_section_admin_tools" = "Admin tools"; "room_participants_action_section_direct_chats" = "Direct chats"; -"room_participants_action_section_devices" = "Devices"; -"room_participants_action_section_other" = "Other"; +"room_participants_action_section_devices" = "Sessions"; +"room_participants_action_section_other" = "Options"; +"room_participants_action_section_security" = "Security"; "room_participants_action_invite" = "Invite"; "room_participants_action_leave" = "Leave this room"; @@ -256,6 +258,22 @@ "room_participants_action_start_voice_call" = "Start voice call"; "room_participants_action_start_video_call" = "Start video call"; "room_participants_action_mention" = "Mention"; +"room_participants_action_security_status_verified" = "Verified"; +"room_participants_action_security_status_verify" = "Verify"; +"room_participants_action_security_status_warning" = "Warning"; +"room_participants_action_security_status_loading" = "Loading…"; + +"room_participants_security_loading" = "Loading…"; +"room_participants_security_information_room_not_encrypted" = "Messages in this room are not end-to-end encrypted."; +"room_participants_security_information_room_encrypted" = "Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them."; + +"room_member_power_level_admin_in" = "Admin in %@"; +"room_member_power_level_moderator_in" = "Moderator in %@"; +"room_member_power_level_custom_in" = "Custom (%@) in %@"; + +"room_member_power_level_short_admin" = "Admin"; +"room_member_power_level_short_moderator" = "Mod"; +"room_member_power_level_short_custom" = "Custom"; // Chat "room_jump_to_first_unread" = "Jump to first unread message"; @@ -275,7 +293,7 @@ "room_message_reply_to_short_placeholder" = "Send a reply…"; "room_offline_notification" = "Connectivity to the server has been lost."; "room_unsent_messages_notification" = "Messages not sent. %@ or %@ now?"; -"room_unsent_messages_unknown_devices_notification" = "Message not sent due to unknown devices being present. %@ or %@ now?"; +"room_unsent_messages_unknown_devices_notification" = "Message not sent due to unknown sessions being present. %@ or %@ now?"; "room_ongoing_conference_call" = "Ongoing conference call. Join as %@ or %@."; "room_ongoing_conference_call_with_close" = "Ongoing conference call. Join as %@ or %@. %@ it."; "room_ongoing_conference_call_close" = "Close"; @@ -341,13 +359,13 @@ "media_type_accessibility_sticker" = "Sticker"; // Unknown devices -"unknown_devices_alert_title" = "Room contains unknown devices"; -"unknown_devices_alert" = "This room contains unknown devices which have not been verified.\nThis means there is no guarantee that the devices belong to the users they claim to.\nWe recommend you go through the verification process for each device before continuing, but you can resend the message without verifying if you prefer."; +"unknown_devices_alert_title" = "Room contains unknown sessions"; +"unknown_devices_alert" = "This room contains unknown sessions which have not been verified.\nThis means there is no guarantee that the sessions belong to the users they claim to.\nWe recommend you go through the verification process for each session before continuing, but you can resend the message without verifying if you prefer."; "unknown_devices_send_anyway" = "Send Anyway"; "unknown_devices_call_anyway" = "Call Anyway"; "unknown_devices_answer_anyway" = "Answer Anyway"; "unknown_devices_verify" = "Verify…"; -"unknown_devices_title" = "Unknown devices"; +"unknown_devices_title" = "Unknown sessions"; // Room Title "room_title_new_room" = "New room"; @@ -389,7 +407,7 @@ "settings_other" = "OTHER"; "settings_labs" = "LABS"; "settings_flair" = "Show flair where allowed"; -"settings_devices" = "DEVICES"; +"settings_devices" = "SESSIONS"; "settings_cryptography" = "CRYPTOGRAPHY"; "settings_key_backup" = "KEY BACKUP"; "settings_deactivate_account" = "DEACTIVATE ACCOUNT"; @@ -416,6 +434,8 @@ "settings_three_pids_management_information_part2" = "Discovery"; "settings_three_pids_management_information_part3" = "."; +"settings_security" = "SECURITY"; + "settings_enable_push_notif" = "Notifications on this device"; "settings_show_decrypted_content" = "Show decrypted content"; "settings_global_settings_info" = "Global notification settings are available on your %@ web client"; @@ -458,8 +478,6 @@ "settings_labs_room_members_lazy_loading_error_message" = "Your homeserver does not support lazy loading of room members yet. Try later."; "settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi"; "settings_labs_message_reaction" = "React to messages with emoji"; -"settings_labs_dm_key_verification" = "Key verification by direct message"; -"settings_labs_cross_signing" = "Cross-Signing"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; @@ -486,27 +504,27 @@ "settings_add_3pid_password_message" = "To continue, please enter your password"; "settings_add_3pid_invalid_password_message" = "Invalid password"; -"settings_crypto_device_name" = "Device Public Name: "; -"settings_crypto_device_id" = "\nDevice ID: "; -"settings_crypto_device_key" = "\nDevice key:\n"; +"settings_crypto_device_name" = "Session name: "; +"settings_crypto_device_id" = "\nSession ID: "; +"settings_crypto_device_key" = "\nSession key:\n"; "settings_crypto_export" = "Export keys"; -"settings_crypto_blacklist_unverified_devices" = "Encrypt to verified devices only"; +"settings_crypto_blacklist_unverified_devices" = "Encrypt to verified sessions only"; "settings_deactivate_my_account" = "Deactivate my account"; "settings_key_backup_info" = "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages."; -"settings_key_backup_info_checking" = "Checking..."; -"settings_key_backup_info_none" = "Your keys are not being backed up from this device."; -"settings_key_backup_info_signout_warning" = "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device."; +"settings_key_backup_info_checking" = "Checking…"; +"settings_key_backup_info_none" = "Your keys are not being backed up from this session."; +"settings_key_backup_info_signout_warning" = "Connect this session to key backup before signing out to avoid losing any keys that may only be on this device."; "settings_key_backup_info_version" = "Key Backup Version: %@"; "settings_key_backup_info_algorithm" = "Algorithm: %@"; -"settings_key_backup_info_valid" = "This device is backing up your keys."; -"settings_key_backup_info_not_valid" = "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward."; -"settings_key_backup_info_progress" = "Backing up %@ keys..."; +"settings_key_backup_info_valid" = "This session is backing up your keys."; +"settings_key_backup_info_not_valid" = "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward."; +"settings_key_backup_info_progress" = "Backing up %@ keys…"; "settings_key_backup_info_progress_done" = "All keys backed up"; -"settings_key_backup_info_trust_signature_unknown" = "Backup has a signature from device with ID: %@"; -"settings_key_backup_info_trust_signature_valid" = "Backup has a valid signature from this device"; +"settings_key_backup_info_trust_signature_unknown" = "Backup has a signature from session with ID: %@"; +"settings_key_backup_info_trust_signature_valid" = "Backup has a valid signature from this session"; "settings_key_backup_info_trust_signature_valid_device_verified" = "Backup has a valid signature from %@"; "settings_key_backup_info_trust_signature_valid_device_unverified" = "Backup has a signature from %@"; "settings_key_backup_info_trust_signature_invalid_device_verified" = "Backup has an invalid signature from %@"; @@ -515,11 +533,11 @@ "settings_key_backup_button_create" = "Start using Key Backup"; "settings_key_backup_button_restore" = "Restore from Backup"; "settings_key_backup_button_delete" = "Delete Backup"; -"settings_key_backup_button_connect" = "Connect this device to Key Backup"; +"settings_key_backup_button_connect" = "Connect this session to Key Backup"; "settings_key_backup_delete_confirmation_prompt_title" = "Delete Backup"; "settings_key_backup_delete_confirmation_prompt_msg" = "Are you sure? You will lose your encrypted messages if your keys are not backed up properly."; -"settings_devices_description" = "A device's public name is visible to people you communicate with"; +"settings_devices_description" = "A session's public name is visible to people you communicate with"; "settings_discovery_no_identity_server" = "You are not currently using an identity server. To be discoverable by existing contacts you known, add one."; "settings_discovery_terms_not_signed" = "Agree to the Identity Server (%@) Terms of Service to allow yourself to be discoverable by email address or phone number."; @@ -543,6 +561,33 @@ "settings_identity_server_no_is_description" = "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one above."; +// Security settings +"security_settings_title" = "Security"; +"security_settings_crypto_sessions" = "MY SESSIONS"; +"security_settings_crypto_sessions_loading" = "Loading sessions…"; +"security_settings_crypto_sessions_description" = "Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup."; + +"security_settings_backup" = "MESSAGE BACKUP"; + +"security_settings_crosssigning" = "CROSS-SIGNING"; + +"security_settings_cryptography" = "CRYPTOGRAPHY"; +"security_settings_export_keys_manually" = "Export keys manually"; + +"security_settings_advanced" = "ADVANCED"; +"security_settings_blacklist_unverified_devices" = "Never send messages to untrusted sessions"; +"security_settings_blacklist_unverified_devices_description" = "Verify all of a users sessions to mark them as trusted and send messages to them."; + + +// Manage session +"manage_session_title" = "Manage session"; +"manage_session_info" = "SESSION INFO"; +"manage_session_name" = "Session name"; +"manage_session_trusted" = "Trusted by you"; +"manage_session_not_trusted" = "Not trusted"; +"manage_session_sign_out" = "Sign out of this session"; + + // Identity server settings "identity_server_settings_title" = "Identity Server"; @@ -616,7 +661,7 @@ "room_details_advanced_enable_e2e_encryption"="Enable encryption (warning: cannot be disabled again!)"; "room_details_advanced_e2e_encryption_enabled"="Encryption is enabled in this room"; "room_details_advanced_e2e_encryption_disabled"="Encryption is not enabled in this room."; -"room_details_advanced_e2e_encryption_blacklist_unverified_devices"="Encrypt to verified devices only"; +"room_details_advanced_e2e_encryption_blacklist_unverified_devices"="Encrypt to verified sessions only"; "room_details_fail_to_update_avatar" = "Fail to update the room photo"; "room_details_fail_to_update_room_name" = "Fail to update the room name"; "room_details_fail_to_update_topic" = "Fail to update the topic"; @@ -695,7 +740,7 @@ "event_formatter_jitsi_widget_added" = "VoIP conference added by %@"; "event_formatter_jitsi_widget_removed" = "VoIP conference removed by %@"; "event_formatter_rerequest_keys_part1_link" = "Re-request encryption keys"; -"event_formatter_rerequest_keys_part2" = " from your other devices."; +"event_formatter_rerequest_keys_part2" = " from your other sessions."; "event_formatter_message_edited_mention" = "(edited)"; // Others @@ -718,8 +763,8 @@ // Call "call_incoming_voice_prompt" = "Incoming voice call from %@"; "call_incoming_video_prompt" = "Incoming video call from %@"; -"call_incoming_voice" = "Incoming call..."; -"call_incoming_video" = "Incoming video call..."; +"call_incoming_voice" = "Incoming call…"; +"call_incoming_video" = "Incoming video call…"; "call_already_displayed" = "There is already a call in progress."; "call_jitsi_error" = "Failed to join the conference call."; @@ -737,7 +782,7 @@ // Crypto "e2e_enabling_on_app_update" = "Riot now supports end-to-end encryption but you need to log in again to enable it.\n\nYou can do it now or later from the application settings."; -"e2e_need_log_in_again" = "You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver.\nThis is a once off; sorry for the inconvenience."; +"e2e_need_log_in_again" = "You need to log back in to generate end-to-end encryption keys for this session and submit the public key to your homeserver.\nThis is a once off; sorry for the inconvenience."; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "New Key Backup"; @@ -784,7 +829,7 @@ // Widget Picker "widget_picker_title" = "Integrations"; -"widget_picker_manage_integrations" = "Manage integrations..."; +"widget_picker_manage_integrations" = "Manage integrations…"; // Room widget permissions "room_widget_permission_title" = "Load Widget"; @@ -807,9 +852,9 @@ // Room key request dialog "e2e_room_key_request_title" = "Encryption key request"; -"e2e_room_key_request_message_new_device" = "You added a new device '%@', which is requesting encryption keys."; -"e2e_room_key_request_message" = "Your unverified device '%@' is requesting encryption keys."; -"e2e_room_key_request_start_verification" = "Start verification..."; +"e2e_room_key_request_message_new_device" = "You added a new session '%@', which is requesting encryption keys."; +"e2e_room_key_request_message" = "Your unverified session '%@' is requesting encryption keys."; +"e2e_room_key_request_start_verification" = "Start verification…"; "e2e_room_key_request_share_without_verifying" = "Share without verifying"; "e2e_room_key_request_ignore_request" = "Ignore request"; @@ -854,7 +899,7 @@ // Re-request confirmation dialog "rerequest_keys_alert_title" = "Request Sent"; -"rerequest_keys_alert_message" = "Please launch Riot on another device that can decrypt the message so it can send the keys to this device."; +"rerequest_keys_alert_message" = "Please launch Riot on another device that can decrypt the message so it can send the keys to this session."; // MARK: Key backup setup @@ -913,6 +958,9 @@ "key_backup_recover_invalid_recovery_key_title" = "Recovery Key Mismatch"; "key_backup_recover_invalid_recovery_key" = "Backup could not be decrypted with this key: please verify that you entered the correct recovery key."; +// Recover from private key +"key_backup_recover_from_private_key_info" = "Restoring backup…"; + // Recover from passphrase "key_backup_recover_from_passphrase_info" = "Use your recovery passphrase to unlock your secure message history"; @@ -948,7 +996,7 @@ // Recover "key_backup_recover_banner_title" = "Never lose encrypted messages"; -"key_backup_recover_connent_banner_subtitle" = "Connect this device to Key Backup"; +"key_backup_recover_connent_banner_subtitle" = "Connect this session to Key Backup"; // MARK: Sign out warning @@ -969,35 +1017,69 @@ "sign_out_key_backup_in_progress_alert_cancel_action" = "I'll wait"; // MARK: - Device Verification -"device_verification_title" = "Verify device"; +"device_verification_title" = "Verify session"; +"key_verification_user_title" = "Verify user"; + "device_verification_security_advice" = "For maximum security, we recommend you do this in person or use another trusted means of communication"; "device_verification_cancelled" = "The other party cancelled the verification."; "device_verification_cancelled_by_me" = "The verification has been cancelled. Reason: %@"; -"device_verification_error_cannot_load_device" = "Cannot load device information."; +"device_verification_error_cannot_load_device" = "Cannot load session information."; // Mark: Incoming "device_verification_incoming_title" = "Incoming Verification Request"; -"device_verification_incoming_description_1" = "Verify this device to mark it as trusted. Trusting devices of partners gives you extra peace of mind when using end-to-end encrypted messages."; -"device_verification_incoming_description_2" = "Verifying this device will mark it as trusted, and also mark your device as trusted to the partner."; +"device_verification_incoming_description_1" = "Verify this session to mark it as trusted. Trusting sessions of partners gives you extra peace of mind when using end-to-end encrypted messages."; +"device_verification_incoming_description_2" = "Verifying this session will mark it as trusted, and also mark your session as trusted to the partner."; // MARK: Start "device_verification_start_title" = "Verify by comparing a short text string"; -"device_verification_start_wait_partner" = "Waiting for partner to accept..."; +"device_verification_start_wait_partner" = "Waiting for partner to accept…"; "device_verification_start_use_legacy" = "Nothing appearing? Not all clients supports interactive verification yet. Use legacy verification."; "device_verification_start_verify_button" = "Begin Verifying"; "device_verification_start_use_legacy_action" = "Use Legacy Verification"; +// MARK: Self verification start + +"device_verification_self_verify_alert_title" = "New Sign In"; +"device_verification_self_verify_alert_message" = "Use this session to verify your new one, granting it access to encrypted messages: %@\nIf you didn’t sign in to this session, your account may be compromised."; +"device_verification_self_verify_alert_cancel_action" = "This wasn't me"; + +"device_verification_self_verify_start_verify_action" = "Start verification"; +"device_verification_self_verify_start_information" = "Use this session to verify your new one, granting it access to encrypted messages."; +"device_verification_self_verify_start_waiting" = "Waiting…"; + +// MARK: Self verification wait + +"device_verification_self_verify_wait_title" = "Complete security"; +"device_verification_self_verify_wait_information" = "Use an existing session to verify this new one, granting it access to encrypted messages."; +"device_verification_self_verify_wait_waiting" = "Waiting…"; + // MARK: Verify -"device_verification_verify_title_emoji" = "Verify this device by confirming the following emoji appear on the screen of the partner"; -"device_verification_verify_title_number" = "Verify this device by confirming the following numbers appear on the screen of the partner"; -"device_verification_verify_wait_partner" = "Waiting for partner to confirm..."; + +// Device + +"device_verification_verify_title_emoji" = "Verify this session by confirming the following emoji appear on the screen of the partner"; +"device_verification_verify_title_number" = "Verify this session by confirming the following numbers appear on the screen of the partner"; +"device_verification_verify_wait_partner" = "Waiting for partner to confirm…"; + +// User + +"key_verification_verify_user_title_emoji" = "Verify this user by confirming the following unique emoji appears on their screen, in the same order."; +"key_verification_verify_user_title_number" = "Verify this user by confirming the following numbers appear on their screen, in the same order."; // MARK: Verified + +// Device + "device_verification_verified_title" = "Verified!"; -"device_verification_verified_description_1" = "You've successfully verified this device."; +"device_verification_verified_description_1" = "You've successfully verified this session."; "device_verification_verified_description_2" = "Secure messages with this user are end-to-end encrypted and not able to be read by third parties."; "device_verification_verified_got_it_button" = "Got it"; +// User + +"key_verification_verified_user_description_1" = "You’ve successfully verified this user."; +"key_verification_verified_user_description_2" = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties."; + // MARK: Emoji "device_verification_emoji_dog" = "Dog"; "device_verification_emoji_cat" = "Cat"; @@ -1087,3 +1169,92 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Add an identity server in your settings to invite by email."; "error_not_supported_on_mobile" = "You can't do this from %@ mobile."; + +// MARK: - Key Verification + +"key_verification_bootstrap_not_setup_title" = "Error"; +"key_verification_bootstrap_not_setup_message" = "You have to bootstrap cross-signing"; + +// Tiles + +"key_verification_tile_request_incoming_title" = "Verification request"; +"key_verification_tile_request_outgoing_title" = "Verification sent"; + +"key_verification_tile_request_status_data_loading" = "Data loading…"; +"key_verification_tile_request_status_waiting" = "Waiting…"; +"key_verification_tile_request_status_expired" = "Expired"; +"key_verification_tile_request_status_cancelled_by_me" = "You cancelled"; +"key_verification_tile_request_status_cancelled" = "%@ cancelled"; +"key_verification_tile_request_status_accepted" = "You accepted"; + +"key_verification_tile_request_incoming_approval_accept" = "Accept"; +"key_verification_tile_request_incoming_approval_decline" = "Decline"; + +"key_verification_tile_conclusion_done_title" = "Verified"; +"key_verification_tile_conclusion_warning_title" = "Unstrusted sign in"; + +// Incoming key verification request + +"key_verification_incoming_request_incoming_alert_message" = "%@ wants to verify"; + +// MARK: QR code + +"key_verification_verify_qr_code_title" = "Verify by scanning"; +"key_verification_verify_qr_code_information" = "Scan the code to securely verify each other."; +"key_verification_verify_qr_code_scan_code_action" = "Scan their code"; +"key_verification_verify_qr_code_cannot_scan_action" = "Can't scan?"; + +"key_verification_verify_qr_code_other_scan_my_code_title" = "Did the other user successfully scan the QR code?"; + +"key_verification_verify_qr_code_scan_other_code_success_title" = "Code validated!"; +"key_verification_verify_qr_code_scan_other_code_success_message" = "QR code has been successfully validated."; + +// MARK: Scan confirmation + +// Scanning +"key_verification_scan_confirmation_scanning_title" = "Almost there! Waiting for confirmation…"; +"key_verification_scan_confirmation_scanning_user_waiting_other" = "Waiting for %@…"; +"key_verification_scan_confirmation_scanning_device_waiting_other" = "Waiting for other device…"; + +// Scanned +"key_verification_scan_confirmation_scanned_title" = "Almost there!"; +"key_verification_scan_confirmation_scanned_user_information" = "Is %@ showing the same shield?"; +"key_verification_scan_confirmation_scanned_device_information" = "Is the other device showing the same shield?"; + +// MARK: - User verification + +// Start + +"user_verification_start_verify_action" = "Start verification"; +"user_verification_start_information_part1" = "For extra security, verify "; +"user_verification_start_information_part2" = " by checking a one-time code on both your devices."; +"user_verification_start_waiting_partner" = "Waiting for %@…"; +"user_verification_start_additional_information" = "To be secure, do this in person or use another way to communicate."; + +// Sessions list + +"user_verification_sessions_list_user_trust_level_trusted_title" = "Trusted"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Warning"; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Unknown"; +"user_verification_sessions_list_information" = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties."; +"user_verification_sessions_list_table_title" = "Sessions"; +"user_verification_sessions_list_session_trusted" = "Trusted"; +"user_verification_sessions_list_session_untrusted" = "Not trusted"; + +// Session details + +"user_verification_session_details_trusted_title" = "Trusted"; +"user_verification_session_details_untrusted_title" = "Warning"; + +"user_verification_session_details_information_trusted_current_user" = "This session is trusted for secure messaging because you verified it:"; +"user_verification_session_details_information_trusted_other_user_part1" = "This session is trusted for secure messaging because "; +"user_verification_session_details_information_trusted_other_user_part2" = " verified it:"; + +"user_verification_session_details_information_untrusted_current_user" = "Verify this session to mark it as trusted & grant it access to encrypted messages:"; +"user_verification_session_details_information_untrusted_other_user" = " signed in using a new session:"; + +"user_verification_session_details_additional_information_untrusted_other_user" = "Until this user trusts this session, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it."; +"user_verification_session_details_additional_information_untrusted_current_user" = "If you didn’t sign in to this session, your account may be compromised."; + +"user_verification_session_details_verify_action_current_user" = "Verify"; +"user_verification_session_details_verify_action_other_user" = "Manually verify"; diff --git a/Riot/Assets/eu.lproj/Localizable.strings b/Riot/Assets/eu.lproj/Localizable.strings index a17db7e88..b39ed2681 100644 --- a/Riot/Assets/eu.lproj/Localizable.strings +++ b/Riot/Assets/eu.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ erabiltzailea %@ gelan"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ erabiltzaileak eranskailu bat bidali du"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@(e)k egiaztatu nahi du"; diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index cea12ce87..8c7879fa5 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -68,7 +68,7 @@ "auth_missing_phone" = "Telefono zenbakia falta da"; "auth_missing_email_or_phone" = "E-mail helbidea edo telefono zenbakia falta da"; "auth_password_dont_match" = "Pasahitzak ez datoz bat"; -"auth_forgot_password" = "Pasahitza ahaztu duzu?"; +"auth_forgot_password" = "Pasahitza ahaztuta?"; "auth_use_server_options" = "Erabili zerbitzari pertsonalizatuaren ezarpenak (aurreratua)"; "auth_email_validation_message" = "Egiaztatu zure e-mail helbidea erregistroarekin jarraitzeko"; "auth_recaptcha_message" = "Hasiera-zerbitzari honek robot bat ez zarela egiaztatu nahi du"; @@ -924,3 +924,29 @@ "room_widget_permission_widget_id_permission" = "Trepetaren ID-a"; "room_widget_permission_room_id_permission" = "Gelaren ID-a"; "widget_picker_manage_integrations" = "Integrazioak kudeatu..."; +// Accessibility +"accessibility_checkbox_label" = "egiaztaketa-koadroa"; +"settings_labs_dm_key_verification" = "Gako egiaztaketa mezu zuzenaren bidez"; +"settings_labs_cross_signing" = "Zeharkako sinadura"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Markatu %@ onartzeko"; +"key_verification_tile_request_incoming_title" = "Egiaztaketa eskaria"; +"key_verification_tile_request_outgoing_title" = "Egiaztaketa bidalita"; +"key_verification_tile_request_status_data_loading" = "Datuak kargatzen…"; +"key_verification_tile_request_status_waiting" = "Itxaroten…"; +"key_verification_tile_request_status_expired" = "Iraungita"; +"key_verification_tile_request_status_cancelled_by_me" = "Utzi duzu"; +"key_verification_tile_request_status_cancelled" = "%@(e)k utzita"; +"key_verification_tile_request_status_accepted" = "Onartu duzu"; +"key_verification_tile_request_incoming_approval_accept" = "Onartu"; +"key_verification_tile_request_incoming_approval_decline" = "Ukatu"; +"key_verification_tile_conclusion_done_title" = "Egiaztatuta"; +"key_verification_tile_conclusion_warning_title" = "Fidagarritasun gabeko saio hasiera"; +"key_verification_incoming_request_incoming_alert_message" = "%@(e)k egiaztatu nahi du"; +"settings_security" = "SEGURTASUNA"; +"settings_labs_enable_cross_signing" = "Gaitu zeharkako sinatzea erabiltzaileko egiaztatzeko eta ez saioko (garapenean)"; +// Security settings +"security_settings_title" = "Segurtasuna"; +"security_settings_crypto_sessions" = "NIRE SAIOAK"; +"security_settings_backup" = "MEZUEN BABES-KOPIA"; +"security_settings_advanced" = "AURRERATUA"; +"security_settings_blacklist_unverified_devices" = "Ez bidali inoiz mezuak egiaztatu gabeko saioetara"; diff --git a/Riot/Assets/fr.lproj/Localizable.strings b/Riot/Assets/fr.lproj/Localizable.strings index 7ad385fcb..7a273fa86 100644 --- a/Riot/Assets/fr.lproj/Localizable.strings +++ b/Riot/Assets/fr.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ dans %@"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ a envoyé un sticker"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ veut vérifier"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 84603356f..23af40ea7 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -169,7 +169,7 @@ "room_participants_action_section_admin_tools" = "Outils d'administration"; "room_participants_action_section_direct_chats" = "Discussions directes"; "room_participants_action_section_devices" = "Appareils"; -"room_participants_action_section_other" = "Autres"; +"room_participants_action_section_other" = "Options"; "room_participants_action_invite" = "Inviter"; "room_participants_action_leave" = "Quitter ce salon"; "room_participants_action_remove" = "Exclure de ce salon"; @@ -944,3 +944,69 @@ "service_terms_modal_policy_checkbox_accessibility_hint" = "Cochez pour accepter %@"; "settings_labs_dm_key_verification" = "Vérification de clé par message direct"; "settings_labs_cross_signing" = "Signature croisée"; +"key_verification_tile_request_incoming_title" = "Demande de vérification"; +"key_verification_tile_request_outgoing_title" = "Vérification envoyée"; +"key_verification_tile_request_status_data_loading" = "Chargement des données…"; +"key_verification_tile_request_status_waiting" = "En attente…"; +"key_verification_tile_request_status_expired" = "Expiré"; +"key_verification_tile_request_status_cancelled_by_me" = "Vous avez annulé"; +"key_verification_tile_request_status_cancelled" = "%@ a annulé"; +"key_verification_tile_request_status_accepted" = "Vous avez accepté"; +"key_verification_tile_request_incoming_approval_accept" = "Accepter"; +"key_verification_tile_request_incoming_approval_decline" = "Refuser"; +"key_verification_tile_conclusion_done_title" = "Vérifié"; +"key_verification_tile_conclusion_warning_title" = "Connexion non approuvée"; +"key_verification_incoming_request_incoming_alert_message" = "%@ veut vérifier"; +"settings_labs_enable_cross_signing" = "Activer la vérification croisée pour vérifier par utilisateurs plutôt que par appareil (en développement)"; +"settings_security" = "SÉCURITÉ"; +// Security settings +"security_settings_title" = "Sécurité"; +"security_settings_crypto_sessions" = "MES SESSIONS"; +"security_settings_crypto_sessions_description" = "Faites confiance à des sessions pour leur accorder l’accès aux messages chiffrés de bout en bout. Si vous ne reconnaissez pas une session, modifiez votre mot de passe de connexion et réinitialisez votre mot de passe de messages utilisé pour la sauvegarde des messages."; +"security_settings_backup" = "SAUVEGARDE DES MESSAGES"; +"security_settings_advanced" = "AVANCÉ"; +"security_settings_blacklist_unverified_devices" = "Ne jamais envoyer de message aux sessions non fiables"; +"security_settings_blacklist_unverified_devices_description" = "Vérifier toutes les sessions d’un utilisateur pour les marquer comme fiables et leur envoyer des messages."; +"security_settings_export_keys_manually" = "Exporter les clés manuellement"; +// Manage session +"manage_session_title" = "Gérer la session"; +"manage_session_info" = "INFORMATIONS DE LA SESSION"; +"manage_session_name" = "Nom de l’appareil"; +"manage_session_trusted" = "Vous lui faites confiance"; +"manage_session_not_trusted" = "Vous ne lui faites pas confiance"; +"manage_session_sign_out" = "Se déconnecter de cet appareil"; +"room_participants_action_section_security" = "Sécurité"; +"room_participants_action_security_status_verified" = "Vérifié"; +"room_participants_action_security_status_verify" = "Vérifier"; +"room_participants_action_security_status_warning" = "Attention"; +"room_participants_security_loading" = "Chargement…"; +"room_participants_security_information_room_not_encrypted" = "Les messages de ce salon ne sont pas chiffrés de bout en bout."; +"room_participants_security_information_room_encrypted" = "Les messages de ce salon sont chiffrés de bout en bout.\n\nVos messages sont sécurisés avec des verrous et seuls vous et le destinataire avez les clés uniques pour les déverrouiller."; +"key_verification_user_title" = "Vérifier l’utilisateur"; +"key_verification_verify_user_title_emoji" = "Vérifiez cet utilisateur en confirmant que les émojis uniques suivants apparaissent sur son écran, dans le même ordre."; +"key_verification_verify_user_title_number" = "Vérifiez cet utilisateur en confirmant que les nombres suivants apparaissent sur son écran, dans le même ordre."; +"key_verification_verified_user_description_1" = "Vous avez vérifié cet utilisateur."; +"key_verification_verified_user_description_2" = "Les messages avec cet utilisateur dans ce salon sont chiffrés de bout en bout et ne peuvent être lus par des tiers."; +"user_verification_start_verify_action" = "Commencer la vérification"; +"user_verification_start_information_part1" = "Pour une meilleur sécurité, vérifiez "; +"user_verification_start_information_part2" = " en comparant un code à usage unique sur vos deux appareils."; +"user_verification_start_waiting_partner" = "Nous attendons %@…"; +"user_verification_start_additional_information" = "Pour une meilleure sécurité, faites cela en personne ou utilisez un autre moyen de communication."; +"user_verification_sessions_list_user_trust_level_trusted_title" = "Fiable"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Attention"; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Inconnu"; +"user_verification_sessions_list_information" = "Les messages avec cet utilisateur dans ce salon sont chiffrés de bout en bout et ne peuvent pas être lus par des tiers."; +"user_verification_sessions_list_table_title" = "Sessions"; +"user_verification_sessions_list_session_trusted" = "Fiable"; +"user_verification_sessions_list_session_untrusted" = "Non fiable"; +"user_verification_session_details_trusted_title" = "Fiable"; +"user_verification_session_details_untrusted_title" = "Attention"; +"user_verification_session_details_information_trusted_current_user" = "Cette session est fiable pour la messagerie sécurisée car vous l’avez vérifiée :"; +"user_verification_session_details_information_trusted_other_user_part1" = "Cet appareil est fiable pour la messagerie sécurisée car "; +"user_verification_session_details_information_trusted_other_user_part2" = " l’a vérifié :"; +"user_verification_session_details_information_untrusted_current_user" = "Vérifiez cette session pour la marquer comme fiable et lui permettre d’accéder aux messages sécurisés :"; +"user_verification_session_details_information_untrusted_other_user" = " s’est connecté en utilisant un nouvel appareil :"; +"user_verification_session_details_additional_information_untrusted_other_user" = "Tant que cet utilisateur ne fera pas confiance à cet appareil, les messages envoyés par et vers lui seront marqués par des avertissements. Sinon, vous pouvez le vérifier manuellement."; +"user_verification_session_details_additional_information_untrusted_current_user" = "Si vous ne vous êtes pas connecté à cette session, votre compte est peut-être compromis."; +"user_verification_session_details_verify_action_current_user" = "Vérifier"; +"user_verification_session_details_verify_action_other_user" = "Vérifier manuellement"; diff --git a/Riot/Assets/hu.lproj/Localizable.strings b/Riot/Assets/hu.lproj/Localizable.strings index 1b800160b..7ca6c4d4a 100644 --- a/Riot/Assets/hu.lproj/Localizable.strings +++ b/Riot/Assets/hu.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@ -ban/ben"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ matricát küldött"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ ellenőrizni szeretné"; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index ebcca5dcc..a129ff54a 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -13,7 +13,7 @@ "leave" = "Elhagyás"; "remove" = "Eltávolítás"; "invite" = "Meghívás"; -"retry" = "Újrapróbál"; +"retry" = "Újra"; "cancel" = "Mégse"; "save" = "Ment"; // Room Details @@ -180,7 +180,7 @@ "room_participants_action_section_admin_tools" = "Adminisztrátori eszközök"; "room_participants_action_section_direct_chats" = "Közvetlen csevegések"; "room_participants_action_section_devices" = "Eszközök"; -"room_participants_action_section_other" = "Egyéb"; +"room_participants_action_section_other" = "Opciók"; "room_participants_action_invite" = "Meghívó"; "room_participants_action_leave" = "Szoba elhagyása"; "room_participants_action_remove" = "Eltávolítás a szobából"; @@ -946,3 +946,69 @@ "service_terms_modal_policy_checkbox_accessibility_hint" = "Az engedélyezéshez jelöld be: %@"; "settings_labs_dm_key_verification" = "Kulcs ellenőrzés közvetlen üzenetben"; "settings_labs_cross_signing" = "Kereszt-aláírás"; +"key_verification_tile_request_incoming_title" = "Ellenőrzési kérés"; +"key_verification_tile_request_outgoing_title" = "Ellenőrzés kérése elküldve"; +"key_verification_tile_request_status_data_loading" = "Adat betöltés…"; +"key_verification_tile_request_status_waiting" = "Várakozik…"; +"key_verification_tile_request_status_expired" = "Lejárt"; +"key_verification_tile_request_status_cancelled_by_me" = "Megszakítottad"; +"key_verification_tile_request_status_cancelled" = "%@ megszakította"; +"key_verification_tile_request_status_accepted" = "Elfogadtad"; +"key_verification_tile_request_incoming_approval_accept" = "Elfogad"; +"key_verification_tile_request_incoming_approval_decline" = "Elutasít"; +"key_verification_tile_conclusion_done_title" = "Hitelesített"; +"key_verification_tile_conclusion_warning_title" = "Megbízhatatlan belépés"; +"key_verification_incoming_request_incoming_alert_message" = "%@ ellenőrizni szeretné"; +"settings_labs_enable_cross_signing" = "Kereszt-aláírás engedélyezése a felhasználó alapú azonosításhoz az eszköz alapú helyett (fejlesztés alatt)"; +"settings_security" = "BIZTONSÁG"; +// Security settings +"security_settings_title" = "Biztonság"; +"security_settings_crypto_sessions" = "MUNKAMENETEIM"; +"security_settings_crypto_sessions_description" = "Bízz meg a munkamenetbe a végpontok között titkosított üzenetek hozzáféréséhez. Ha nem ismered fel a munkamenetet, változtasd meg a bejelentkezési jelszavad és az Üzenet Mentéshez használt jelszavad."; +"security_settings_backup" = "ÜZENET MENTÉS"; +"security_settings_advanced" = "HALADÓ"; +"security_settings_blacklist_unverified_devices" = "Soha ne küldj üzenetet megbízhatatlan munkamenetekbe"; +"security_settings_blacklist_unverified_devices_description" = "Ellenőrizd minden felhasználó minden munkamenetét, hogy megbízhatónak tudd jelölni azokat és üzenet küldhess nekik."; +"security_settings_export_keys_manually" = "Kulcsok kimentése kézzel"; +// Manage session +"manage_session_title" = "Munkamenet kezelése"; +"manage_session_info" = "MUNKAMENET INFORMÁCIÓ"; +"manage_session_name" = "Eszköznév"; +"manage_session_trusted" = "Szerinted megbízható"; +"manage_session_not_trusted" = "Megbízhatatlan"; +"manage_session_sign_out" = "Kijelentkezés erről az eszközről"; +"room_participants_action_section_security" = "Biztonság"; +"room_participants_action_security_status_verified" = "Hitelesített"; +"room_participants_action_security_status_verify" = "Ellenőriz"; +"room_participants_action_security_status_warning" = "Figyelmeztetés"; +"room_participants_security_loading" = "Betöltés…"; +"room_participants_security_information_room_not_encrypted" = "Az üzenetek a szobában nincsenek végponttól végpontig titkosítva."; +"room_participants_security_information_room_encrypted" = "A szobában az üzenetek végponttól végpontig titkosítva vannak.\n\nAz üzeneteidet lakattal vannak biztosítva és csak neked és a címzetteknek van meg a kulcs hozzá."; +"key_verification_user_title" = "Felhasználó ellenőrzése"; +"key_verification_verify_user_title_emoji" = "Hitelesítheted a felhasználót, ha megerősíted, hogy az alábbi egyedi emoji-k azok amik megjelentek a képernyőjén."; +"key_verification_verify_user_title_number" = "Hitelesítheted a felhasználót, ha megerősíted, hogy az alábbi számok jelentek meg az ő képernyőjén is."; +"key_verification_verified_user_description_1" = "Sikeresen ellenőrizted ezt a felhasználót."; +"key_verification_verified_user_description_2" = "Az üzenetek ezzel a felhasználóval végponttól végpontig titkosítva vannak és más nem tudja elolvasni."; +"user_verification_start_verify_action" = "Ellenőrzés elindítása"; +"user_verification_start_information_part1" = "A nagyobb biztonság érdekében ellenőrizd. "; +"user_verification_start_information_part2" = " az egyszeri kóddal mindkét eszközön."; +"user_verification_start_waiting_partner" = "Várakozás erre: %@…"; +"user_verification_start_additional_information" = "A biztonság érdekében ezt végezd el személyesen vagy egy megbízható kommunikációs csatornán."; +"user_verification_sessions_list_user_trust_level_trusted_title" = "Megbízható"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Figyelmeztetés"; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Ismeretlen"; +"user_verification_sessions_list_information" = "Az üzenetek ezzel a felhasználóval végponttól végpontig titkosítva vannak és más nem tudja elolvasni."; +"user_verification_sessions_list_table_title" = "Munkamenetek"; +"user_verification_sessions_list_session_trusted" = "Megbízható"; +"user_verification_sessions_list_session_untrusted" = "Megbízhatatlan"; +"user_verification_session_details_trusted_title" = "Megbízható"; +"user_verification_session_details_untrusted_title" = "Figyelmeztetés"; +"user_verification_session_details_information_trusted_current_user" = "Ez a munkamenet megbízhatónak van jelölve a biztonságos üzenetküldéshez, mert ellenőrizted:"; +"user_verification_session_details_information_trusted_other_user_part1" = "Ez az eszköz megbízhatónak van jelölve a biztonságos üzenetküldéshez, mert "; +"user_verification_session_details_information_trusted_other_user_part2" = " ellenőrizve:"; +"user_verification_session_details_information_untrusted_current_user" = "A munkamenet megbízhatónak való jelöléséhez és a titkosított üzenetekhez való hozzáférés engedélyezéséhez hitelesítsd azt:"; +"user_verification_session_details_information_untrusted_other_user" = " új eszközzel bejelentkezve:"; +"user_verification_session_details_additional_information_untrusted_other_user" = "Amíg a felhasználó nem jelöli megbízhatónak ezt az eszközt addig az eszközről küldött és az eszközre érkező üzenetek figyelmeztetéssel lesznek ellátva. Vagy te is ellenőrizheted az eszközt."; +"user_verification_session_details_additional_information_untrusted_current_user" = "Ha nem te jelentkeztél be ebbe a munkamenetbe akkor a fiókodat feltörték."; +"user_verification_session_details_verify_action_current_user" = "Ellenőriz"; +"user_verification_session_details_verify_action_other_user" = "Manuális ellenőrzés"; diff --git a/Riot/Assets/it.lproj/Localizable.strings b/Riot/Assets/it.lproj/Localizable.strings index daa2aefdd..78d7e2fba 100644 --- a/Riot/Assets/it.lproj/Localizable.strings +++ b/Riot/Assets/it.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Messaggio ricevuto da %@"; +"MSG_FROM_USER" = "%@ ha inviato un messaggio"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ ha scritto in %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ ha inviato un'immagine %@"; +"IMAGE_FROM_USER" = "%@ ha invitato un'immagine %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ ha inviato un'immagine %@ in %@"; /* A single unread message in a room */ @@ -50,3 +50,8 @@ "VOICE_CONF_NAMED_FROM_USER" = "Chiamata di gruppo da %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Video chiamata di gruppo da %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ ha inviato un adesivo"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ vuole verificare"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 30b1e8d50..c7b1f9d63 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -1,7 +1,7 @@ // Titles "title_home" = "Home"; "title_favourites" = "Preferiti"; -"title_people" = "Persone"; +"title_people" = "Chat dirette"; "title_rooms" = "Stanze"; "store_full_description" = "Comunica, a modo tuo.\n\nUn'app di messaggistica, sotto il tuo controllo e interamente flessibile. Riot ti permette di comunicare a modo tuo. Creata per [matrix] - lo standard per le comunicazioni aperte, decentralizzate.\n\nOttieni un account matrix.org gratuito, ottieni il tuo server su https://modular.im, o usa un altro server Matrix.\n\nPerché scegliere Riot.im?\n\n• COMUNICAZIONE COMPLETA: crea stanze per i tuoi team, i tuoi amici, la tua comunità - come preferisci! Chatta, condividi file, aggiungi widget e fai videochiamate vocali - tutto gratuito.\n\n• GRANDI INTEGRAZIONI: usa Riot.im con gli strumenti che conosci ed ami. Con Riot.im puoi addirittura chattare con utenti e gruppi su altre applicazioni di messaggistica.\n\n• PRIVATO E SICURO: tieni segrete le tue conversazioni. Una crittografia end-to-end allo stato dell'arte assicura che le comunicazioni private restino tali.\n\n• APERTO, NON CHIUSO: open source e costruito su Matrix. Possiedi i tuoi dati ospitando il tuo server personale, o scegliendone uno di cui ti fidi.\n\n• OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi totalmente sincronizzata tra i tuoi dispositivi ed online su https://riot.im."; "title_groups" = "Comunità"; @@ -12,12 +12,12 @@ "invite" = "Invita"; "cancel" = "Annulla"; "save" = "Salva"; -"join" = "Unisciti"; +"join" = "Entra"; "decline" = "Rifiuta"; "accept" = "Accetto"; "preview" = "Anteprima"; "rename" = "Rinomina"; -"collapse" = "collassa"; +"collapse" = "riduci"; "send_to" = "Invia a %@"; // Authentication "auth_login" = "Accedi"; @@ -33,7 +33,7 @@ "auth_email_placeholder" = "Indirizzo email"; "auth_optional_phone_placeholder" = "Numero di telefono (opzionale)"; "auth_phone_placeholder" = "Numero di telefono"; -"auth_repeat_password_placeholder" = "Ripeti password"; +"auth_repeat_password_placeholder" = "Ripeti la password"; "auth_repeat_new_password_placeholder" = "Conferma la nuova password"; "auth_home_server_placeholder" = "URL (es. https://matrix.org)"; "auth_identity_server_placeholder" = "URL (es. https://vector.im)"; @@ -55,9 +55,9 @@ "auth_forgot_password" = "Password dimenticata?"; "auth_email_validation_message" = "Per favore controlla la tua email per proseguire la registrazione"; "auth_msisdn_validation_title" = "In attesa di verifica"; -"auth_msisdn_validation_message" = "Un SMS è stato spedito con il codice di attivazione. Per favore inserisci il codice qui sotto."; +"auth_msisdn_validation_message" = "Ti è stato spedito un SMS con il codice di attivazione. Per favore inserisci il codice qui sotto."; "auth_msisdn_validation_error" = "Impossibile verificare il numero di telefono."; -"auth_reset_password_message" = "Per ripristinare la password, inserisci l'indirizzo email associato al tuo account:"; +"auth_reset_password_message" = "Per ripristinare la password inserisci l'indirizzo email associato al tuo account:"; // String for App Store "store_short_description" = "Conversazioni sicure e decentralizzate"; // Actions @@ -67,33 +67,33 @@ "create" = "Crea"; "start" = "Inizia"; "retry" = "Riprova"; -"on" = "Attivo"; -"off" = "Disattivo"; +"on" = "On"; +"off" = "Off"; "camera" = "Fotocamera"; -"voice" = "Voce"; +"voice" = "Audio"; "video" = "Video"; -"active_call" = "Chiamata attiva"; -"active_call_details" = "Chiamata attiva (%@)"; +"active_call" = "Chiamata in corso"; +"active_call_details" = "Chiamata in corso (%@)"; "later" = "Più avanti"; "sending" = "Inviando"; "auth_submit" = "Invia"; "auth_login_single_sign_on" = "Accedi con single sign-on"; -"auth_untrusted_id_server" = "Il server identità non è affidabile"; +"auth_untrusted_id_server" = "L'affidabilità dell'Identity Server non è stata accertata"; "auth_password_dont_match" = "Le password non corrispondono"; "auth_username_in_use" = "Nome utente in uso"; "auth_email_not_found" = "Invio dell'email fallito: Questo indirizzo email non è stato trovato"; -"auth_use_server_options" = "Usa opzioni server personalizzate (avanzate)"; -"auth_recaptcha_message" = "Questo server home vorrebbe assicurarsi che tu non sia un robot"; -"auth_reset_password_missing_email" = "L'indirizzo email associato al tuo account deve essere inserito."; -"auth_reset_password_missing_password" = "Una nuova password deve essere inserita."; -"auth_reset_password_email_validation_message" = "Un'email è stata inviata a %s. Appena avrai seguito il link lì contenuto, clicca qui sotto."; +"auth_use_server_options" = "Usa le opzioni personalizzate del Server (avanzate)"; +"auth_recaptcha_message" = "Questo Home Server vuol esser certo che tu non sia un robot"; +"auth_reset_password_missing_email" = "Va inserito l'indirizzo email associato al tuo account."; +"auth_reset_password_missing_password" = "Va inserita una nuova password."; +"auth_reset_password_email_validation_message" = "Un'email è stata inviata a %s. Dopo aver cliccato il link che contiene, clicca qui sotto."; "auth_reset_password_next_step_button" = "Ho verificato il mio indirizzo email"; "auth_reset_password_error_unauthorized" = "Verifica indirizzo email fallita: assicurati di aver cliccato sul link contenuto nell'email"; -"auth_reset_password_error_not_found" = "Il tuo indirizzo email non sembra associato a nessun ID Matrix su questo server home."; -"auth_reset_password_success_message" = "La tua password è stata ripristinata.\n\n Sei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per riabilitare le notifiche, riconnettiti su ciascun dispositivo."; -"auth_add_email_and_phone_warning" = "La registrazione con email e numero di telefono in una volta sola non è ancora supportata (relative API in sviluppo). Sarà utilizzato solo il numero di telefono. Puoi aggiungere la tua email al tuo profilo dalle impostazioni."; -"auth_accept_policies" = "Per favore, rivedere e accettare le politiche di questo server home:"; -"auth_autodiscover_invalid_response" = "Risposta alla scoperta di un homeserver non valida"; +"auth_reset_password_error_not_found" = "Il tuo indirizzo email non sembra associato a nessun ID utente registrato su questo Home Server."; +"auth_reset_password_success_message" = "La tua password è stata ripristinata.\n\nSei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per riabilitare le notifiche, riconnettiti su ciascun dispositivo."; +"auth_add_email_and_phone_warning" = "Al momento non è ancora possibile registrarsi contemporaneamente con indirizzo email e numero di telefono e quindi verrà utilizzato solo il numero di telefono. Puoi aggiungere la tua email al tuo profilo dall menù Impostazioni."; +"auth_accept_policies" = "Per favore, rileggi e accetta i termini di servizio di questo Home Server:"; +"auth_autodiscover_invalid_response" = "Risposta Home Server non valida"; // Chat creation "room_creation_title" = "Nuova chat"; "room_creation_account" = "Account"; @@ -108,13 +108,13 @@ "room_creation_make_public_prompt_msg" = "Sei sicuro di voler rendere pubblica questa chat? Chiunque potrà leggere i tuoi messaggi e unirsi alla chat."; "room_creation_keep_private" = "Mantieni privata"; "room_creation_make_private" = "Rendi privata"; -"room_creation_wait_for_creation" = "Una stanza sta già venendo creata. Per favore attendi."; +"room_creation_wait_for_creation" = "Una stanza è già in fase di creazione. Per favore attendi."; "room_creation_invite_another_user" = "Cerca / invita per ID utente, nome o email"; // Room recents "room_recents_directory_section" = "ELENCO STANZE"; "room_recents_directory_section_network" = "Rete"; "room_recents_favourites_section" = "PREFERITI"; -"room_recents_people_section" = "PERSONE"; +"room_recents_people_section" = "CHAT DIRETTE"; "room_recents_conversations_section" = "STANZE"; "room_recents_no_conversation" = "Nessuna stanza"; "room_recents_low_priority_section" = "BASSA PRIORITÀ"; @@ -124,7 +124,7 @@ "room_recents_create_empty_room" = "Crea stanza"; "room_recents_join_room" = "Entra nella stanza"; "room_recents_join_room_title" = "Entra in una stanza"; -"room_recents_join_room_prompt" = "Digita ID o soprannome (alias)"; +"room_recents_join_room_prompt" = "Digita ID stanza o il suo nome"; // People tab "people_invites_section" = "INVITI"; "people_conversation_section" = "CONVERSAZIONI"; @@ -144,19 +144,19 @@ "search_no_result" = "Nessun risultato"; "search_in_progress" = "Ricerca…"; // Directory -"directory_cell_title" = "Esplora elenco"; +"directory_cell_title" = "Esplora l'elenco"; "directory_cell_description" = "%tu stanze"; "directory_search_results_title" = "Esplora risultati elenco"; "directory_search_results" = "%tu risultati trovati per %@"; "directory_search_results_more_than" = ">%tu risultati trovati per %@"; "directory_searching_title" = "Ricerca negli elenchi…"; -"directory_search_fail" = "Fallita la ricerca di informazioni"; +"directory_search_fail" = "La ricerca di informazioni è fallita"; // Contacts "contacts_address_book_section" = "CONTATTI LOCALI"; "contacts_address_book_matrix_users_toggle" = "Solo utenti Matrix"; "contacts_address_book_no_contact" = "Nessun contatto locale"; -"contacts_address_book_permission_required" = "Permesso richiesto per accedere ai contatti"; -"contacts_address_book_permission_denied" = "Non hai permesso a Riot di accedere ai tuoi contatti"; +"contacts_address_book_permission_required" = "Riot deve essere autorizzato per poter accedere alla Rubrica locale"; +"contacts_address_book_permission_denied" = "Non hai autorizzato Riot ad accedere alla Rubrica locale"; "contacts_user_directory_section" = "ELENCO UTENTI"; "contacts_user_directory_offline_section" = "ELENCO UTENTI (offline)"; // Chat participants @@ -164,17 +164,17 @@ "room_participants_add_participant" = "Aggiungi membro"; "room_participants_one_participant" = "1 membro"; "room_participants_multi_participants" = "%d membri"; -"room_participants_leave_prompt_title" = "Lascia stanza"; +"room_participants_leave_prompt_title" = "Esci dalla stanza"; "room_participants_leave_prompt_msg" = "Sei sicuro di voler uscire dalla stanza?"; "room_participants_remove_prompt_title" = "Conferma"; "room_participants_remove_prompt_msg" = "Sei sicuro di voler rimuovere %@ da questa chat?"; "room_participants_remove_third_party_invite_msg" = "Finché non esistono le API, la rimozione di inviti di terze parti non è possibile"; "room_participants_invite_prompt_title" = "Conferma"; "room_participants_invite_prompt_msg" = "Sei sicuro di voler invitare %@ in questa chat?"; -"room_participants_filter_room_members" = "Filtra i membri della stanza"; +"room_participants_filter_room_members" = "Cerca tra i membri della stanza"; "room_participants_invite_another_user" = "Cerca / invita per ID utente, nome o email"; "room_participants_invite_malformed_id_title" = "Errore durante l'invito"; -"room_participants_invite_malformed_id" = "ID incorretto. Dovrebbe essere un indirizzo email o un ID Matrix come '@localpart:domain'"; +"room_participants_invite_malformed_id" = "ID incorretto. Dovrebbe essere un indirizzo email o un ID utente tipo '@localpart:domain'"; "room_participants_invited_section" = "INVITATI"; "room_participants_online" = "Online"; "room_participants_offline" = "Offline"; @@ -182,23 +182,23 @@ "room_participants_idle" = "Inattivo"; "room_participants_now" = "adesso"; "room_participants_ago" = "fa"; -"room_participants_action_section_admin_tools" = "Strumenti admin"; +"room_participants_action_section_admin_tools" = "Strumenti Admin"; "room_participants_action_section_direct_chats" = "Chat dirette"; "room_participants_action_section_devices" = "Dispositivi"; -"room_participants_action_section_other" = "Altro"; +"room_participants_action_section_other" = "Opzioni"; "room_participants_action_invite" = "Invita"; -"room_participants_action_leave" = "Lascia questa stanza"; +"room_participants_action_leave" = "Esci da questa stanza"; "room_participants_action_remove" = "Rimuovi da questa stanza"; -"room_participants_action_ban" = "Bandisci da questa stanza"; -"room_participants_action_unban" = "Togli il bando"; +"room_participants_action_ban" = "Banna da questa stanza"; +"room_participants_action_unban" = "Rimuovi il ban"; "room_participants_action_ignore" = "Nascondi tutti i messaggi di questo utente"; "room_participants_action_unignore" = "Mostra tutti i messaggi di questo utente"; "room_participants_action_set_default_power_level" = "Ripristina ad utente normale"; -"room_participants_action_set_moderator" = "Rendi moderatore"; -"room_participants_action_set_admin" = "Rendi amministratore"; +"room_participants_action_set_moderator" = "Nomina moderatore"; +"room_participants_action_set_admin" = "Nomina amministratore"; "room_participants_action_start_new_chat" = "Inizia nuova chat"; -"room_participants_action_start_voice_call" = "Avvia chiamata vocale"; -"room_participants_action_start_video_call" = "Avvia videochiamata"; +"room_participants_action_start_voice_call" = "Avvia chiamata audio"; +"room_participants_action_start_video_call" = "Avvia chiamata video"; "room_participants_action_mention" = "Citazione"; // Chat "room_jump_to_first_unread" = "Vai al primo messaggio non letto"; @@ -207,62 +207,62 @@ "room_one_user_is_typing" = "%@ sta scrivendo…"; "room_two_users_are_typing" = "%@ e %@ stanno scrivendo…"; "room_many_users_are_typing" = "%@, %@ e altri stanno scrivendo…"; -"room_message_placeholder" = "Invia un messaggio (non criptato)…"; -"room_message_reply_to_placeholder" = "Invia una risposta (non criptata)…"; +"room_message_placeholder" = "Invia un messaggio (non cifrato)…"; +"room_message_reply_to_placeholder" = "Invia una risposta (non cifrata)…"; "room_message_unable_open_link_error_message" = "Impossibile aprire il link."; "room_do_not_have_permission_to_post" = "Non hai il permesso di pubblicare in questa stanza"; -"encrypted_room_message_placeholder" = "Invia un messaggio criptato…"; -"encrypted_room_message_reply_to_placeholder" = "Invia una risposta criptata…"; +"encrypted_room_message_placeholder" = "Invia un messaggio cifrato…"; +"encrypted_room_message_reply_to_placeholder" = "Invia una risposta cifrata…"; "room_message_short_placeholder" = "Invia un messaggio…"; "room_message_reply_to_short_placeholder" = "Invia una risposta…"; "room_offline_notification" = "La connessione al server è stata persa."; "room_unsent_messages_notification" = "Messaggi non inviati. %1$s o %2$s ora?"; -"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa di dispositivi sconosciuti qui presenti. %1$s o %2$s ora?"; -"room_ongoing_conference_call" = "Chiamata di conferenza in corso. Unisciti come %@ o %@."; -"room_ongoing_conference_call_with_close" = "Chiamata di conferenza in corso. Unisciti come %@ o %@. %@."; +"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa della presenza di dispositivi sconosciuti. %1$s o %2$s ora?"; +"room_ongoing_conference_call" = "Avvio conferenza. Unisciti come %@ o %@."; +"room_ongoing_conference_call_with_close" = "Avvio conferenza. Unisciti come %@ o %@. %@."; "room_ongoing_conference_call_close" = "Chiudi"; -"room_conference_call_no_power" = "Hai bisogno dei permessi per gestire le chiamate di gruppo in questa stanza"; -"room_prompt_resend" = "Rinvia tutto"; +"room_conference_call_no_power" = "Non hai permessi sufficienti per avviare una conferenza in questa stanza"; +"room_prompt_resend" = "Reinvia tutto"; "room_prompt_cancel" = "annulla tutto"; "room_resend_unsent_messages" = "Reinvia i messaggi non spediti"; -"room_delete_unsent_messages" = "Elimina messaggi non spediti"; +"room_delete_unsent_messages" = "Elimina i messaggi non spediti"; "room_event_action_copy" = "Copia"; "room_event_action_quote" = "Cita"; "room_event_action_redact" = "Rimuovi"; "room_event_action_more" = "Altro"; "room_event_action_share" = "Condividi"; -"room_event_action_permalink" = "Collegamento permanente"; -"room_event_action_view_source" = "Vedi sorgente"; -"room_event_action_view_decrypted_source" = "Vedi sorgente decriptata"; -"room_event_action_report" = "Segnala contenuto"; +"room_event_action_permalink" = "Permalink"; +"room_event_action_view_source" = "Vedi il codice sorgente"; +"room_event_action_view_decrypted_source" = "Vedi il codice sorgente decifrato"; +"room_event_action_report" = "Segnala il contenuto"; "room_event_action_report_prompt_reason" = "Motivo della segnalazione"; "room_event_action_kick_prompt_reason" = "Motivo per cui rimuovi questo utente"; "room_event_action_ban_prompt_reason" = "Motivo per cui banni questo utente"; -"room_event_action_report_prompt_ignore_user" = "Vuoi nascondere tutti i messaggi da questo utente?"; +"room_event_action_report_prompt_ignore_user" = "Vuoi nascondere tutti i messaggi di questo utente?"; "room_event_action_save" = "Salva"; "room_event_action_resend" = "Reinvia"; "room_event_action_delete" = "Elimina"; "room_event_action_cancel_send" = "Annulla invio"; "room_event_action_cancel_download" = "Annulla download"; -"room_event_action_view_encryption" = "Informazioni crittografia"; -"room_warning_about_encryption" = "La crittografia da-utente-a-utente è in fase sperimentale e potrebbe non esser ancora affidabile.\n\nNon dovesti ancora farci affidamento per proteggere i tuoi dati.\n\nI dispositivi non potranno decrittare la cronologia dei messaggi precedenti all'entrata nella stanza.\n\nI messaggi crittografati non saranno visibili dalle applicazioni che non hanno ancora implementato la crittografia."; +"room_event_action_view_encryption" = "Informazioni sulla crittografia"; +"room_warning_about_encryption" = "La crittografia E2E è in fase sperimentale e potrebbe non esser ancora affidabile.\n\nNon dovesti ancora farci affidamento per proteggere i tuoi dati.\n\nI dispositivi non potranno decifrare i messaggi precedenti alla tua entrata nella stanza.\n\nI messaggi cifrati non saranno visibili dalle applicazioni per Matrix che non hanno ancora implementato la crittografia."; "room_event_failed_to_send" = "Invio fallito"; "room_action_send_photo_or_video" = "Invia foto o video"; "room_action_send_sticker" = "Invia sticker"; -"room_replacement_information" = "Questa stanza è stata sostituita e non è più attiva."; +"room_replacement_information" = "Questa stanza è stata sostituita da un'altra e non è più attiva."; "room_replacement_link" = "La conversazione continua qui."; -"room_predecessor_information" = "Questa stanza è la continuazione di un'altra conversazione."; -"room_predecessor_link" = "Clicca per vedere messaggi più vecchi."; +"room_predecessor_information" = "Questa stanza contiene una conversazione cominciata altrove."; +"room_predecessor_link" = "Clicca per vedere i messaggi più vecchi."; "room_resource_limit_exceeded_message_contact_1" = " Per favore "; "room_resource_limit_exceeded_message_contact_2_link" = "contatta l'amministratore del servizio"; "room_resource_limit_exceeded_message_contact_3" = " per continuare ad usare questo servizio."; -"room_resource_usage_limit_reached_message_1_default" = "Questo server home ha superato uno dei limiti delle risorse, pertanto "; -"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Questo server home ha raggiunto il limite mensile di utenti attivi, pertanto "; +"room_resource_usage_limit_reached_message_1_default" = "Questo Home Server ha superato uno dei limiti delle risorse, pertanto "; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Questo Home Server ha raggiunto il limite mensile di utenti attivi, pertanto "; "room_resource_usage_limit_reached_message_2" = "alcuni utenti non potranno accedere."; "room_resource_usage_limit_reached_message_contact_3" = " per aumentare questo limite."; // Unknown devices "unknown_devices_alert_title" = "La stanza contiene dispositivi sconosciuti"; -"unknown_devices_alert" = "Questa stanza contiene dispositivi sconosciuti che non sono stati verificati.\nCiò significa che non esiste alcuna garanzia che i dispositivi siano davvero quelli di chi dice di possederli.\nPrima di continuare si consiglia di effettuare la verifica di ogni dispositivo, ma se vuoi è comunque possibile rispedire il messaggio senza fare alcuna verifica."; +"unknown_devices_alert" = "Questa stanza contiene dispositivi sconosciuti che non sono stati verificati.\nNon v'è alcuna garanzia che le persone che utilizzano quei dispositivi siano davvero chi dicono d'essere.\nSi consiglia di verificare ogni dispositivo prima di continuare, ma se si preferisce, è comunque possibile inviare ugualmente il messaggio anche senza la verifica."; "unknown_devices_send_anyway" = "Invia comunque"; "unknown_devices_call_anyway" = "Chiama comunque"; "unknown_devices_answer_anyway" = "Rispondi comunque"; @@ -276,20 +276,20 @@ "room_title_members" = "%@ membri"; "room_title_one_member" = "1 membro"; // Room Preview -"room_preview_invitation_format" = "Sei stato invitato ad unirti in questa stanza da %@"; -"room_preview_subtitle" = "Questa è l'anteprima della stanza. Le interazioni con la stanza sono disabilitate."; -"room_preview_unlinked_email_warning" = "Questo invito è stato spedito da %@, che non è associato a questo account. È possibile che tu voglia connetterti con un altro account o aggiungere questo indirizzo email al tuo account."; -"room_preview_try_join_an_unknown_room" = "Stai provando ad accedere a %@. Desideri entrare per partecipare alla discussione?"; +"room_preview_invitation_format" = "Sei stato invitato ad entrare in questa stanza da %@"; +"room_preview_subtitle" = "Questa è l'anteprima della stanza. Le interazioni sono disabilitate."; +"room_preview_unlinked_email_warning" = "Questo invito è stato spedito a %@, che non è associato a questo account. Puoi aggiungere questa email al tuo account o provare ad accedere con un account differente."; +"room_preview_try_join_an_unknown_room" = "Stai provando ad accedere a %s. Desideri entrare per partecipare alla discussione?"; "room_preview_try_join_an_unknown_room_default" = "una stanza"; // Settings "settings_title" = "Impostazioni"; -"account_logout_all" = "Sconnetti tutti gli account"; -"settings_config_no_build_info" = "Nessuna informazione di build"; +"account_logout_all" = "Disconnetti tutti gli account"; +"settings_config_no_build_info" = "Nessuna informazione su questa versione"; "settings_mark_all_as_read" = "Segna tutti i messaggi come letti"; "settings_report_bug" = "Segnala errore"; "settings_clear_cache" = "Elimina cache"; -"settings_config_home_server" = "L'Homeserver è %@"; -"settings_config_identity_server" = "L'Identity server è %@"; +"settings_config_home_server" = "L'Home Server è %@"; +"settings_config_identity_server" = "L'Identity Server è %@"; "settings_config_user_id" = "Connesso come %@"; "settings_user_settings" = "IMPOSTAZIONI UTENTE"; "settings_notifications_settings" = "IMPOSTAZIONI NOTIFICHE"; @@ -300,14 +300,14 @@ "settings_advanced" = "AVANZATE"; "settings_other" = "ALTRO"; "settings_labs" = "LABORATORIO"; -"settings_flair" = "Mostra predisposizione se permesso"; +"settings_flair" = "Mostra la predisposizione quando è consentito"; "settings_devices" = "DISPOSITIVI"; "settings_cryptography" = "CRITTOGRAFIA"; "settings_key_backup" = "BACKUP DELLE CHIAVI"; "settings_deactivate_account" = "DISATTIVA ACCOUNT"; "settings_sign_out" = "Disconnetti"; "settings_sign_out_confirmation" = "Sei sicuro?"; -"settings_sign_out_e2e_warn" = "Perderai le tue chiavi di crittografia da-utente-a-utente. Questo significa che non potrai più leggere i vecchi messaggi nelle stanze crittografate su questo dispositivo."; +"settings_sign_out_e2e_warn" = "Perderai le tue chiavi crittografiche E2E. Questo significa che non potrai più leggere i vecchi messaggi nelle stanze cifrate su questo dispositivo."; "settings_profile_picture" = "Immagine profilo"; "settings_display_name" = "Nome visualizzato"; "settings_first_name" = "Nome"; @@ -324,13 +324,13 @@ "settings_night_mode" = "Modalità notte"; "settings_fail_to_update_profile" = "Errore nell'aggiornamento del profilo"; "settings_enable_push_notif" = "Notifiche per questo dispositivo"; -"settings_show_decrypted_content" = "Mostra contenuto decrittato"; -"settings_global_settings_info" = "Le impostazioni globali di notifica sono disponibili nel tuo %@ web client"; +"settings_show_decrypted_content" = "Mostra il contenuto decifrato"; +"settings_global_settings_info" = "Le impostazioni di notifica avanzate sono disponibili nel tuo %@ web client"; "settings_pin_rooms_with_missed_notif" = "Segna le stanze con notifiche perse"; "settings_pin_rooms_with_unread" = "Segna le stanze con messaggi non letti"; "settings_on_denied_notification" = "Le notifiche non sono permesse per %@, abilitale nelle impostazioni del tuo dispositivo"; "settings_enable_callkit" = "Chiamate integrate"; -"settings_callkit_info" = "Ricevi le chiamate in arrivo nel blocca schermo. Mostra le chiamate Riot nella cronologia di chiamate del dispositivo. Se iCloud è attivo, questa cronologia sarà condivisa con Apple."; +"settings_callkit_info" = "Ricevi le chiamate in arrivo sul blocca schermo. Mostra le chiamate Riot nella cronologia di chiamate del dispositivo. Se iCloud è attivo, questa cronologia sarà condivisa con Apple."; "settings_ui_language" = "Lingua"; "settings_ui_theme" = "Tema"; "settings_ui_theme_auto" = "Automatico"; @@ -341,12 +341,12 @@ "settings_ui_theme_picker_message" = "\"Automatico\" usa l'impostazione \"Inverti Colori\" del tuo dispositivo"; "settings_unignore_user" = "Mostrare tutti i messaggi da %@?"; "settings_contacts_discover_matrix_users" = "Usa email e numeri di telefono per trovare utenti"; -"settings_contacts_phonebook_country" = "Nazione rubrica telefonica"; -"settings_labs_e2e_encryption" = "Crittografia da-utente-a-utente"; +"settings_contacts_phonebook_country" = "Prefisso telefonico internazionale"; +"settings_labs_e2e_encryption" = "Crittografia E2E"; "settings_labs_e2e_encryption_prompt_message" = "Per finire la configurazione della crittografia devi rieseguire l'accesso."; -"settings_labs_room_members_lazy_loading" = "Caricamento posticipato dei membri della stanza"; -"settings_labs_room_members_lazy_loading_error_message" = "Il tuo Homeserver non supporta ancora il caricamento intelligente dei membri delle stanze. Prova in seguito."; -"settings_labs_create_conference_with_jitsi" = "Crea una videoconferenza con jitsi"; +"settings_labs_room_members_lazy_loading" = "Caricamento differito dei membri della stanza"; +"settings_labs_room_members_lazy_loading_error_message" = "Il tuo Home Server non supporta ancora il caricamento differito dei membri delle stanze. Prova più avanti."; +"settings_labs_create_conference_with_jitsi" = "Avvia una conferenza usando Jitsi"; "settings_version" = "Versione %@"; "settings_olm_version" = "Versione Olm %@"; "settings_copyright" = "Copyright"; @@ -357,41 +357,41 @@ "settings_privacy_policy_url" = "https://riot.im/privacy"; "settings_third_party_notices" = "Avvisi di terze parti"; "settings_send_crash_report" = "Invia dati di utilizzo anonimi"; -"settings_enable_rageshake" = "Agita con rabbia per segnalare un errore"; +"settings_enable_rageshake" = "Per segnalare un errore agita il dispositivo con rabbia"; "settings_old_password" = "vecchia password"; "settings_new_password" = "nuova password"; "settings_confirm_password" = "conferma password"; "settings_fail_to_update_password" = "Aggiornamento password fallito"; "settings_password_updated" = "La tua password è stata aggiornata"; "settings_crypto_device_name" = "Nome pubblico dispositivo: "; -"settings_crypto_device_id" = "\nID Dispositivo: "; +"settings_crypto_device_id" = "\nID dispositivo: "; "settings_crypto_device_key" = "\nChiave dispositivo:\n"; "settings_crypto_export" = "Esporta chiavi"; -"settings_crypto_blacklist_unverified_devices" = "Crittografa solo per i dispositivi verificati"; +"settings_crypto_blacklist_unverified_devices" = "Cifratura solo per i dispositivi verificati"; "settings_deactivate_my_account" = "Disattiva il mio account"; -"settings_key_backup_info" = "I messaggi crittografati sono protetti con la crittografia da-utente-a-utente. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi."; +"settings_key_backup_info" = "I messaggi nelle stanze cifrate sono protetti con crittografia E2E. Solo tu e il/i destinatario/i avete le chiavi crittografiche per leggere questi messaggi."; "settings_key_backup_info_checking" = "Verifica..."; -"settings_key_backup_info_none" = "Nessun Backup programmato per le Chiavi da questo dispositivo."; -"settings_key_backup_info_signout_warning" = "Connetti questo dispositivo al backup chiavi prima di disconnetterti per evitare di perdere eventuali chiavi presenti solo qui."; +"settings_key_backup_info_none" = "Questo dispositivo non sta facendo il Backup delle chiavi."; +"settings_key_backup_info_signout_warning" = "Prima di disconnetterti effettua un Backup delle chiavi per evitare di perdere eventuali chiavi presenti solo questo dispositivo."; "settings_key_backup_info_version" = "Versione backup chiave: %@"; "settings_key_backup_info_algorithm" = "Algoritmo: %@"; -"settings_key_backup_info_valid" = "Questo dispositivo sta eseguendo il backup delle tue chiavi."; -"settings_key_backup_info_not_valid" = "Questo dispositivo non sta eseguendo il Backup delle tue Chiavi, ma é disponibile un vecchio Backup da cui puoi ripristinarle."; +"settings_key_backup_info_valid" = "Questo dispositivo sta eseguendo il Backup delle tue chiavi."; +"settings_key_backup_info_not_valid" = "Questo dispositivo non sta eseguendo il Backup delle tue Chiavi. Però è disponibile un vecchio Backup da cui puoi ripristinarle."; "settings_key_backup_info_progress" = "Backup di %@ chiavi…"; "settings_key_backup_info_progress_done" = "Backup di tutte le chiavi completato"; -"settings_key_backup_info_trust_signature_unknown" = "Il backup ha una firma dal dispositivo con ID: %@"; +"settings_key_backup_info_trust_signature_unknown" = "Il Backup ha una firma dal dispositivo con ID: %@"; "settings_key_backup_info_trust_signature_valid" = "Il backup ha una firma valida da questo dispositivo"; -"settings_key_backup_info_trust_signature_valid_device_verified" = "Il backup ha una firma valida da %@"; -"settings_key_backup_info_trust_signature_valid_device_unverified" = "Il backup ha una firma da %@"; -"settings_key_backup_info_trust_signature_invalid_device_verified" = "Il backup ha una firma non valida da %@"; -"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Il backup ha una firma non valida da %@"; -"settings_key_backup_button_create" = "Inizia ad usare il backup chiavi"; -"settings_key_backup_button_restore" = "Ripristina da backup"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Il Backup ha una firma valida da %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "Il Backup ha una firma da %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Il Backup ha una firma non valida da %@"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Il Backup ha una firma non valida da %@"; +"settings_key_backup_button_create" = "Inizia ad usare il Backup delle chiavi"; +"settings_key_backup_button_restore" = "Ripristina da Backup"; "settings_key_backup_button_delete" = "Elimina backup"; -"settings_key_backup_delete_confirmation_prompt_title" = "Elimina backup"; -"settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Se non hai eseguito un Backup delle Chiavi perderai i tuoi messaggi crittografati."; +"settings_key_backup_delete_confirmation_prompt_title" = "Elimina Backup"; +"settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Se non hai eseguito un Backup delle chiavi perderai i tuoi messaggi cifrati."; // Room Details -"room_details_title" = "Dettagli canale"; +"room_details_title" = "Dettagli stanza"; "room_details_people" = "Membri"; "room_details_files" = "File"; "room_details_settings" = "Impostazioni"; @@ -407,8 +407,8 @@ "room_details_access_section_anyone_apart_from_guest" = "Chiunque conosca il link della stanza, eccetto gli ospiti"; "room_details_access_section_anyone" = "Chiunque conosca il link della stanza, compresi gli ospiti"; "room_details_access_section_no_address_warning" = "Per poter essere linkata, la stanza deve avere un indirizzo"; -"room_details_access_section_directory_toggle" = "Mostra questa stanza nell'elenco delle stanze"; -"room_details_history_section" = "Chi può leggere la cronologia?"; +"room_details_access_section_directory_toggle" = "Mostra questa stanza nell'elenco delle stanze pubbliche"; +"room_details_history_section" = "Chi può leggere la Timeline?"; "room_details_history_section_anyone" = "Chiunque"; "room_details_history_section_members_only" = "Solo i membri (dal momento in cui questa opzione è stata selezionata)"; "room_details_history_section_members_only_since_invited" = "Solo i membri (dal momento in cui vengono invitati)"; @@ -593,13 +593,13 @@ "key_backup_setup_skip_alert_title" = "Sei sicuro?"; "key_backup_setup_skip_alert_message" = "Se ti disconnetti oppure perdi il dispositivo potresti perdere i tuoi messaggi crittografati."; "key_backup_setup_skip_alert_skip_action" = "Salta"; -"key_backup_setup_intro_title" = "Non perdere mai i messaggi crittografati"; -"key_backup_setup_intro_info" = "I messaggi nelle stanze crittografate sono protetti con crittografia da-utente-a-utente. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi. \n \nFai un backup delle tue chiavi per evitare di perderle."; +"key_backup_setup_intro_title" = "Non perdere mai i messaggi cifrati"; +"key_backup_setup_intro_info" = "I messaggi nelle stanze cifrate sono protetti con crittografia E2E. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi. \n \nFai un backup delle tue chiavi crittografiche per evitare di perderle."; "key_backup_setup_intro_setup_action_without_existing_backup" = "Inizia ad usare il backup chiavi"; "key_backup_setup_intro_manual_export_info" = "(Avanzato)"; "key_backup_setup_intro_manual_export_action" = "Esporta manualmente le chiavi"; "key_backup_setup_passphrase_title" = "Proteggi il tuo backup con una frase d'accesso"; -"key_backup_setup_passphrase_info" = "Salveremo una copia crittografata delle tue chiavi nel tuo Homeserver. Proteggi il tuo backup con una frase di sicurezza per tenerlo sicuro. \n \nPer una massima sicurezza, dovrebbe essere diversa dalla password del tuo account."; +"key_backup_setup_passphrase_info" = "Sul tuo Home Server verrà effettuato un Backup cifrato delle tue chiavi crittograficher. Proteggi il Backup con una password perchè sia al sicuro.\n \nPer una massima sicurezza, la password del Backup dovrebbe essere diversa dalla password del tuo account."; "key_backup_setup_passphrase_passphrase_title" = "Inserisci"; "key_backup_setup_passphrase_passphrase_placeholder" = "Inserisci frase d'accesso"; "key_backup_setup_passphrase_passphrase_valid" = "Bene!"; @@ -613,7 +613,7 @@ "key_backup_setup_passphrase_setup_recovery_key_action" = "(Avanzato) Imposta con chiave di ripristino"; "key_backup_setup_success_title" = "Completato!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Backup delle tue chiavi in corso.\n\nLa tua chiave di ripristino è uno strumento di sicurezza - puoi usarla per recuperare l'accesso ai tuoi messaggi crittografati se dimentichi la tua frase d'accesso. \n\nTieni la tua chiave di ripristino in un luogo sicuro, come un password manager (o una cassaforte)."; +"key_backup_setup_success_from_passphrase_info" = "Backup delle tue chiavi in corso.\n\nIl tuo codice di recupero è un'ancora di salvezza - puoi usarlo per riaccedere ai tuoi messaggi cifrati se dimentichi la password. \n\nSalva il tuo codice di recupero in un luogo sicuro, tipo un password manager (o una cassaforte)."; "key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Salva chiave di ripristino"; "key_backup_setup_success_from_passphrase_done_action" = "Fatto"; // Success from recovery key @@ -642,34 +642,34 @@ "key_backup_recover_done_action" = "Fatto"; "key_backup_setup_banner_title" = "Non perdere mai i messaggi crittografati"; "key_backup_setup_banner_subtitle" = "Inizia ad usare il backup chiavi"; -"key_backup_recover_banner_title" = "Non perdere mai i messaggi crittografati"; +"key_backup_recover_banner_title" = "Non perdere mai i messaggi cifrati"; "sign_out_existing_key_backup_alert_title" = "Sei sicuro di volerti disconnettere?"; "sign_out_existing_key_backup_alert_sign_out_action" = "Disconnetti"; -"sign_out_non_existing_key_backup_alert_title" = "Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi crittografati"; +"sign_out_non_existing_key_backup_alert_title" = "Se adesso ti disconnetti, perderai l'accesso ai tuoi messaggi cifrati"; "sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Inizia ad usare il Backup delle chiavi"; -"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Non voglio i miei messaggi crittografati"; -"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Perderai i tuoi messaggi crittografati"; -"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Perderai l'accesso ai tuoi messaggi crittografati a meno che non fai il Backup delle tue chiavi prima di disconnetterti."; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Non voglio i miei messaggi cifrati"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Perderai i tuoi messaggi cifrati"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Perderai l'accesso ai tuoi messaggi cifrati a meno che tu faccia il Backup delle chiavi prima di disconnetterti."; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Disconnetti"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Backup"; -"sign_out_key_backup_in_progress_alert_title" = "Backup chiavi in corso. Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi crittografati."; -"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Non voglio i miei messaggi crittografati"; +"sign_out_key_backup_in_progress_alert_title" = "Backup delle chiavi in corso. Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi cifrati."; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Non voglio i miei messaggi cifrati"; "sign_out_key_backup_in_progress_alert_cancel_action" = "Attendo"; "close" = "Chiudi"; -"auth_forgot_password_error_no_configured_identity_server" = "Nessun identità server è configurata: aggiungine uno per reimpostare la password."; +"auth_forgot_password_error_no_configured_identity_server" = "Non è stato configurato alcun Identity: aggiungine uno per poter reimpostare la password."; "auth_softlogout_signed_out" = "Sei uscito"; "auth_softlogout_sign_in" = "Accedi"; -"auth_softlogout_reason" = "L'amministratore homeserver (%1$@) ti ha disconnesso dal tuo account %2$@ (%3$@)."; -"auth_softlogout_recover_encryption_keys" = "Accedi per recuperare le chiavi di crittografia archiviate esclusivamente su questo dispositivo. Ti servono per leggere tutti i tuoi messaggi sicuri su qualsiasi dispositivo."; -"auth_softlogout_clear_data" = "Cancella dati personali"; -"auth_softlogout_clear_data_message_1" = "Avviso: i tuoi dati personali (comprese le chiavi di crittografia) sono ancora memorizzati su questo dispositivo."; -"auth_softlogout_clear_data_message_2" = "Cancellalo se hai finito di utilizzare questo dispositivo o desideri accedere a un altro account."; +"auth_softlogout_reason" = "L'amministratore dell'Home Server (%1$@) ti ha disconnesso dal tuo account %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Accedi per recuperare le chiavi crittografiche archiviate su questo dispositivo. Le chiavi ti servono per poter leggere i tuoi messaggi cifrati su altri dispositivi."; +"auth_softlogout_clear_data" = "Cancella i dati personali"; +"auth_softlogout_clear_data_message_1" = "Avviso: i tuoi dati personali (comprese le chiavi crittografiche) sono ancora memorizzati su questo dispositivo."; +"auth_softlogout_clear_data_message_2" = "Cancella se hai finito di utilizzare questo dispositivo o desideri accedere a un altro account."; "auth_softlogout_clear_data_button" = "Cancella tutti i dati"; "auth_softlogout_clear_data_sign_out_title" = "Sei sicuro?"; -"auth_softlogout_clear_data_sign_out_msg" = "Sei sicuro di voler cancellare tutti i dati attualmente memorizzati su questo dispositivo? Accedi di nuovo per accedere ai dati e ai messaggi del tuo account."; -"auth_softlogout_clear_data_sign_out" = "disconnessione"; -"room_creation_error_invite_user_by_email_without_identity_server" = "Nessuna identità server è configurata, quindi non è possibile aggiungere un partecipante con un'e-mail."; -"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Non è stato configurato alcun Identity Server, quindi non è possibile avviare una chat con un contatto tramite e-mail."; +"auth_softlogout_clear_data_sign_out_msg" = "Sei sicuro di voler cancellare tutti i dati memorizzati su questo dispositivo? Accedi di nuovo per gestire dati e messaggi del tuo account."; +"auth_softlogout_clear_data_sign_out" = "Disconnessione"; +"room_creation_error_invite_user_by_email_without_identity_server" = "Non è stato configuato nessun Identity Server e quindi non è possibile aggiungere partecipanti con un'e-mail."; +"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Non è stato configurato alcun Identity Server, quindi non è possibile avviare una chat con qualcuno tramite e-mail."; "room_event_action_reply" = "Rispondi"; "room_event_action_edit" = "Modifica"; "room_event_action_reaction_show_all" = "Mostra tutto"; @@ -681,7 +681,7 @@ "room_message_edits_history_title" = "Modifica messaggio"; "settings_labs_message_reaction" = "Reagisci ai messaggi con emoji"; "room_participants_remove_third_party_invite_prompt_msg" = "Sei sicuro di voler revocare l'invito?"; -"settings_key_backup_button_connect" = "Connetti questo dispositivo al Backup delle Chiavi"; +"settings_key_backup_button_connect" = "Connetti questo dispositivo al Backup delle chiavi"; // Media picker "media_picker_title" = "Media library"; // Image picker @@ -709,7 +709,7 @@ "device_verification_error_cannot_load_device" = "Non si riescono a recuperare le informazioni riguardanti il dipositivo."; // Mark: Incoming "device_verification_incoming_title" = "Richiesta di verifica in corso"; -"device_verification_incoming_description_1" = "Verifica questo dispositivo per contrassegnarlo come fidato. Verificare che i dispositivi dei tuoi contatti siano fidati dà maggiore tranquillità quando usi messaggi crittografati da-utente-a-utente con loro."; +"device_verification_incoming_description_1" = "Verifica questo dispositivo per contrassegnarlo come fidato. Verificare che i dispositivi dei tuoi contatti siano fidati offre maggior sicurezza quando scambi messaggi cifrati con loro."; "device_verification_incoming_description_2" = "Effettuando la verifica, il tuo dispositivo e quello del tuo contatto verranno reciprocamente contrassegnati come fidati."; // MARK: Start "device_verification_start_title" = "Verifica confrontando un breve testo"; @@ -724,7 +724,7 @@ // MARK: Verified "device_verification_verified_title" = "Verificato!"; "device_verification_verified_description_1" = "Hai verificato correttamente questo dispositivo."; -"device_verification_verified_description_2" = "Rendi sicuri i tuoi messaggi con questo contatto usando la crittografia da-utente-a-utente in modo che nessun'altro possa leggerli."; +"device_verification_verified_description_2" = "Cifra i messaggi che scambi con questo contatti in modo che nessun'altro possa leggerli."; "device_verification_verified_got_it_button" = "Fatto"; // MARK: Emoji "device_verification_emoji_dog" = "Cane"; @@ -807,8 +807,8 @@ // MARK: Reaction history "reaction_history_title" = "Reazioni"; // Errors -"error_user_already_logged_in" = "Sembra tu stia tentando di connetterti ad un altro Homeserver. Vuoi disconnetterti?"; -"room_accessiblity_scroll_to_bottom" = "Scorri in fondo"; +"error_user_already_logged_in" = "Sembra tu stia tentando di connetterti ad un altro Home Server. Vuoi disconnetterti?"; +"room_accessiblity_scroll_to_bottom" = "Scorri fino in fondo"; "room_accessibility_search" = "Cerca"; "room_accessibility_integrations" = "Integrazioni"; "room_accessibility_upload" = "Invia"; @@ -819,60 +819,60 @@ "media_type_accessibility_video" = "Video"; "media_type_accessibility_location" = "Posizione"; "media_type_accessibility_file" = "File"; -"media_type_accessibility_sticker" = "Adesivo"; +"media_type_accessibility_sticker" = "Sticker"; // Widget Picker "widget_picker_title" = "Integrazioni"; -"auth_add_email_message_2" = "Imposta un'email per il recupero dell'account, più tardi anche per essere trovabile dalle persone che ti conoscono."; -"auth_add_phone_message_2" = "Imposta un telefono, più tardi anche per essere trovabile dalle persone che ti conoscono."; -"auth_add_email_phone_message_2" = "Imposta un'email per il recupero dell'account. Più tardi usa l'email o il telefono per essere trovabile dalle persone che ti conoscono."; -"auth_email_is_required" = "Nessun server di identità configurato, perciò non puoi aggiungere un indirizzo email per ripristinare la password in futuro."; -"auth_phone_is_required" = "Nessun server di identità configurato, perciò non puoi aggiungere un numero di telefono per ripristinare la password in futuro."; -"auth_reset_password_error_is_required" = "Nessun server di identità configurato: aggiungine uno nelle opzioni server per ripristinare la password."; -"contacts_address_book_no_identity_server" = "Nessun server di identità configurato"; +"auth_add_email_message_2" = "Imposta un'email per il ripristino dell'account in caso di problemi e, se vuoi, anche per farti trovare da chi conosce quell'indirizzo email."; +"auth_add_phone_message_2" = "Aggiungi un numero di telefono se vuoi farti trovare da chi lo conosce."; +"auth_add_email_phone_message_2" = "Imposta un'email per il ripristino dell'account in caso di problemi. Email e telefono potranno essere usati anche per farti trovare dagli altri utenti."; +"auth_email_is_required" = "Non è stato configurato alcun Identity Server perciò non puoi aggiungere un indirizzo email utile a ripristinare la password in caso di problemi."; +"auth_phone_is_required" = "Non è stato configurato alcun Identity Server perciò non puoi aggiungere un numero di telefono utile a ripristinare la password in caso di problemi."; +"auth_reset_password_error_is_required" = "Non è stato configurato alcun Identity Server: aggiungine uno nelle opzioni server per poter ripristinare la password."; +"contacts_address_book_no_identity_server" = "Nessun Identity Server configurato"; "settings_discovery_settings" = "SCOPRI"; -"settings_identity_server_settings" = "SERVER IDENTITÀ"; -"settings_three_pids_management_information_part1" = "Gestisci qui quali indirizzi email o numeri di telefono puoi usare per accedere o recuperare l'account. Controlla chi può trovarti in "; +"settings_identity_server_settings" = "IDENTITY SERVER"; +"settings_three_pids_management_information_part1" = "Configura gli indirizzi email o numeri di telefono che puoi usare per accedere o per ripristinare l'account in caso di problemi. Controlla chi può trovarti in "; "settings_three_pids_management_information_part2" = "Scopri"; "settings_three_pids_management_information_part3" = "."; -"settings_calls_stun_server_fallback_button" = "Permetti server di assistenza alle chiamate di fallback"; -"settings_calls_stun_server_fallback_description" = "Consenti server di assistenza alle chiamate di fallback %@ quando il tuo homeserver non ne offre uno (il tuo indirizzo IP verrà condiviso durante una chiamata)."; -"settings_devices_description" = "Il nome pubblico di un dispositivo è visibile dalle persone con cui comunichi"; -"settings_discovery_no_identity_server" = "Attualmente non stai usando un server di identità. Per essere trovabile dai contatti esistenti che conosci, aggiungine uno."; -"settings_discovery_terms_not_signed" = "Accetta le condizioni di servizio del server di identità (%@) per poter essere trovabile tramite indirizzo email o numero di telefono."; -"settings_discovery_three_pids_management_information_part1" = "Gestisci quali indirizzi email o numeri di telefono gli altri utenti possono usare per trovarti e invitarti nelle stanze. Aggiungi o rimuovi indirizzi email o numeri di telefono da questa lista in "; +"settings_calls_stun_server_fallback_button" = "Permetti chiamate dal Server di appoggio"; +"settings_calls_stun_server_fallback_description" = "Se il tuo Home Server non ne ha un proprio Server d'appoggio verrà usato %s (il Server d'appoggio verrà a conoscenza del tuo indirizzo IP durante le chiamate)."; +"settings_devices_description" = "Il nome pubblico di un dispositivo è visibile alle persone con cui comunichi"; +"settings_discovery_no_identity_server" = "In questo momento non stai usando alcun Identity Server. Per trovare e farti trovare dagli altri utenti, configurane uno qua sotto."; +"settings_discovery_terms_not_signed" = "Accetta i termini di servizio dell'Identity Server (%@) per permettere ad altri utenti di trovarti tramite la tua email o numero di telefono."; +"settings_discovery_three_pids_management_information_part1" = "Configura gli indirizzi email o numeri di telefono con cui gli altri utenti potranno trovarti e invitarti nelle stanze. Aggiungi o rimuovi indirizzi email o numeri di telefono da questa lista in "; "settings_discovery_three_pids_management_information_part2" = "Impostazioni utente"; "settings_discovery_three_pids_management_information_part3" = "."; "settings_discovery_error_message" = "C'è stato un errore. Riprova."; "settings_discovery_three_pid_details_title_email" = "Gestisci email"; -"settings_discovery_three_pid_details_information_email" = "Gestisci le impostazioni di questo indirizzo email che altri utenti possono usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi gli indirizzi email in Accounts."; -"settings_discovery_three_pid_details_title_phone_number" = "Gestisci il numero di telefono"; -"settings_discovery_three_pid_details_information_phone_number" = "Gestisci le impostazioni di questo numero di telefono che altri utenti possono usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi i numeri di telefono in Accounts."; +"settings_discovery_three_pid_details_information_email" = "Configura le impostazioni di questo indirizzo email che altri utenti potranno usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi gli indirizzi email in Accounts."; +"settings_discovery_three_pid_details_title_phone_number" = "Configura il numero di telefono"; +"settings_discovery_three_pid_details_information_phone_number" = "Configura le impostazioni di questo numero di telefono che altri utenti possono usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi i numeri di telefono in Accounts."; "settings_discovery_three_pid_details_share_action" = "Condividi"; "settings_discovery_three_pid_details_revoke_action" = "Cancella"; "settings_discovery_three_pid_details_cancel_email_validation_action" = "Annulla la validazione dell'email"; "settings_discovery_three_pid_details_enter_sms_code_action" = "Inserisci il codice d'attivazione SMS"; "settings_identity_server_description" = "Utilizzando l'Identity Server impostato potrai trovare e farti trovare dai contatti esistenti."; -"settings_identity_server_no_is" = "Nessun Identity Server configurato"; +"settings_identity_server_no_is" = "Non è stato configurato alcun Identity Server"; "settings_identity_server_no_is_description" = "Non stai usando alcun Identity Server. Selezionane uno per trovare e farti trovare dai contatti esistenti."; // Identity server settings -"identity_server_settings_title" = "// Impostazioni dell'Identity Server"; +"identity_server_settings_title" = "Identity Server"; "identity_server_settings_description" = "Per trovare e farti trovare dai tuoi contatti stai usando %@."; "identity_server_settings_no_is_description" = "Non stai usando alcun Identity Server. Selezionane uno per trovare e farti trovare dai contatti esistenti."; "identity_server_settings_place_holder" = "Inserisci un Identity Server"; "identity_server_settings_add" = "Aggiungi"; "identity_server_settings_change" = "Cambia"; -"identity_server_settings_disconnect_info" = "Usare un Identity Server è facoltativo. Se scegli di non usarne uno gli altri utenti non potranno trovarti e tu non potrai invitarli per email o telefono."; +"identity_server_settings_disconnect_info" = "Se ti disconnetti dall'Identity Server gli altri utenti non potranno trovarti e tu non potrai invitarne di nuovi per email o telefono."; "identity_server_settings_disconnect" = "Disconnetti"; -"identity_server_settings_alert_no_terms_title" = "L'Identity Server non ha Termini di servizio"; -"identity_server_settings_alert_no_terms" = "L'Identity Server che hai scelto non ha Termini di servizio. Prosegui solo se ti fidi del gestore del server."; +"identity_server_settings_alert_no_terms_title" = "L'Identity Server non ha fornito dei termini di servizio"; +"identity_server_settings_alert_no_terms" = "L'Identity Server che hai scelto non ha freso noti i propri termini di servizio. Continua solo se ti fidi."; "identity_server_settings_alert_change_title" = "Cambia Identity Server"; -"identity_server_settings_alert_change" = "Vuoi scollegarti dall'Identyty Server %1$@ e connetterti invece a %2$@?"; -"identity_server_settings_alert_disconnect_title" = "Scollega dall'Identity Server"; +"identity_server_settings_alert_change" = "Vuoi scollegarti dall'Identity Server %1$@ e connetterti invece a %2$@?"; +"identity_server_settings_alert_disconnect_title" = "Scollega l'Identity Server"; "identity_server_settings_alert_disconnect" = "Vuoi scollegarti dall'Identity Server %@?"; "identity_server_settings_alert_disconnect_button" = "Scollega"; -"identity_server_settings_alert_disconnect_still_sharing_3pid" = "L'Identity Server %@ può ancora condividere le tue informazioni personali.\n\nTi raccomandiamo di rimuovere tutti i tuoi indirizzi email e numeri di telefono dall'Identity Server prima di scollegarti."; +"identity_server_settings_alert_disconnect_still_sharing_3pid" = "L'Identity Server %@ può ancora condividere le tue informazioni personali.\n\nTi raccomandiamo di rimuovere tutti i tuoi indirizzi email e numeri di telefono dall'Identity Server prima di scollegarlo."; "identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Scollegati comunque"; -"identity_server_settings_alert_error_terms_not_accepted" = "Devi accettare le condizioni di %@ per impostarlo come tuo Identity Server."; +"identity_server_settings_alert_error_terms_not_accepted" = "Devi accettare i termini di servizio di %@ per poterlo impostare come tuo Identity Server."; "identity_server_settings_alert_error_invalid_identity_server" = "%@ non é un Identity Server valido."; "call_no_stun_server_error_title" = "Chiamata fallita a causa di una configurazione errata del server"; "call_no_stun_server_error_message_1" = "Chiedi all'amministratore del tuo Homeserver %@ di configurare un server TURN in modo che le chiamate funzionino come si deve."; @@ -897,7 +897,7 @@ "widget_menu_remove" = "Rimuovi per tutti"; "settings_integrations" = "INTEGRAZIONI"; "settings_integrations_allow_button" = "Gestisci le integrazioni"; -"settings_integrations_allow_description" = "Usa un gestore di integrazioni (%@) per gestire bot, bridge, widget e pacchetti di adesivi.\n\nI gestori di integrazione ricevono dati di configurazione e possono modificare widget, inviare inviti alla stanza, assegnare permessi a tuo nome."; +"settings_integrations_allow_description" = "Usa un Integration Manager (%@) per gestire bot, bridge, widget e pacchetti di sticker.\n\nGli Integration Manager possono ricevere dati di configurazione, modificare widget, mandare inviti alle stanze e modificare permessi a tuo nome."; "widget_integration_manager_disabled" = "Devi attivare il gestore di integrazioni nelle impostazioni"; // Room widget permissions "room_widget_permission_title" = "Carica widget"; @@ -914,5 +914,71 @@ "accessibility_checkbox_label" = "checkbox"; "widget_picker_manage_integrations" = "Gestisci integrazioni..."; "service_terms_modal_policy_checkbox_accessibility_hint" = "Seleziona per accettare %@"; -"settings_labs_dm_key_verification" = "Verifica chiave via messaggio diretto"; +"settings_labs_dm_key_verification" = "Verifica chiave con messaggio diretto"; "settings_labs_cross_signing" = "Firma incrociata"; +"key_verification_tile_request_incoming_title" = "Richiesta di verifica"; +"key_verification_tile_request_outgoing_title" = "Verifica inviata"; +"key_verification_tile_request_status_data_loading" = "Caricamento dati…"; +"key_verification_tile_request_status_waiting" = "In attesa…"; +"key_verification_tile_request_status_expired" = "Scaduta"; +"key_verification_tile_request_status_cancelled_by_me" = "Hai annullato"; +"key_verification_tile_request_status_cancelled" = "%@ ha annullato"; +"key_verification_tile_request_status_accepted" = "Hai accettato"; +"key_verification_tile_request_incoming_approval_accept" = "Accetta"; +"key_verification_tile_request_incoming_approval_decline" = "Rifiuta"; +"key_verification_tile_conclusion_done_title" = "Verificato"; +"key_verification_tile_conclusion_warning_title" = "Accesso non fidato"; +"key_verification_incoming_request_incoming_alert_message" = "%@ vuole verificare"; +"settings_labs_enable_cross_signing" = "Attiva la firma incrociata per la verifica per-utente invece di per-dispositivo (in sviluppo)"; +"settings_security" = "SICUREZZA"; +// Security settings +"security_settings_title" = "Sicurezza"; +"security_settings_crypto_sessions" = "MIE SESSIONI"; +"security_settings_crypto_sessions_description" = "Verifica le sessioni per dare l'accesso ai messaggi cifrati end-to-end. Se non riconosci una sessione, modifica la password di accesso e reimposta la Password Messaggio usata per il Backup Messaggi."; +"security_settings_backup" = "BACKUP MESSAGGI"; +"security_settings_advanced" = "AVANZATE"; +"security_settings_blacklist_unverified_devices" = "Non inviare mai messaggi a sessioni non fidate"; +"security_settings_blacklist_unverified_devices_description" = "Verifica tutte le sessioni di un utente per marcarle come fidate ed invirvi messaggi."; +"security_settings_export_keys_manually" = "Esporta manualmente le chiavi"; +// Manage session +"manage_session_title" = "Gestisci sessione"; +"manage_session_info" = "INFO SESSIONE"; +"manage_session_name" = "Nome dispositivo"; +"manage_session_trusted" = "Di tua fiducia"; +"manage_session_not_trusted" = "Non fidato"; +"manage_session_sign_out" = "Disconnetti da questo dispositivo"; +"room_participants_action_section_security" = "Sicurezza"; +"room_participants_action_security_status_verified" = "Verificato"; +"room_participants_action_security_status_verify" = "Verifica"; +"room_participants_action_security_status_warning" = "Attenzione"; +"room_participants_security_loading" = "Caricamento…"; +"key_verification_user_title" = "Verifica utente"; +"room_participants_security_information_room_not_encrypted" = "I messaggi in questa stanza non sono cifrati end-to-end."; +"room_participants_security_information_room_encrypted" = "I messaggi in questa stanza sono cifrati end-to-end.\n\nI tuoi messaggi sono protetti con lucchetti e solo te ed il destinatario avete le chiavi univoche per sbloccarli."; +"key_verification_verify_user_title_emoji" = "Verifica questo utente confermando che le seguenti emoji appaiono sul suo schermo, nello stesso ordine."; +"key_verification_verify_user_title_number" = "Verifica questo utente confermando che i seguenti numeri appaiono sul suo schermo, nello stesso ordine."; +"key_verification_verified_user_description_1" = "Hai verificato correttamente questo utente."; +"key_verification_verified_user_description_2" = "I messaggi con questo utente in questa stanza sono cifrati end-to-end e non possono essere letti da terze parti."; +"user_verification_start_verify_action" = "Inizia la verifica"; +"user_verification_start_information_part1" = "Per maggiore sicurezza, verifica "; +"user_verification_start_information_part2" = " controllando un codice univoco in entrambi i dispositivi."; +"user_verification_start_waiting_partner" = "In attesa di %@…"; +"user_verification_start_additional_information" = "Per sicurezza, fatelo di persona o usate un altro metodo di comunicazione."; +"user_verification_sessions_list_user_trust_level_trusted_title" = "Fidati"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Attenzione"; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Sconosciuti"; +"user_verification_sessions_list_information" = "I messaggi con questo utente in questa stanza sono cifrati end-to-end e non possono essere letti da terze parti."; +"user_verification_sessions_list_table_title" = "Sessioni"; +"user_verification_sessions_list_session_trusted" = "Fidate"; +"user_verification_sessions_list_session_untrusted" = "Non fidate"; +"user_verification_session_details_trusted_title" = "Fidata"; +"user_verification_session_details_untrusted_title" = "Attenzione"; +"user_verification_session_details_information_trusted_current_user" = "Questa sessione è fidata per comunicazioni sicure perchè l'hai verificata:"; +"user_verification_session_details_information_trusted_other_user_part1" = "Questo dispositivo è fidato per comunicazioni sicure perchè "; +"user_verification_session_details_information_trusted_other_user_part2" = " ha verificato:"; +"user_verification_session_details_information_untrusted_current_user" = "Verifica questa sessione per marcarla come fidata e darle accesso ai messaggi cifrati:"; +"user_verification_session_details_information_untrusted_other_user" = " ha fatto l'accesso con un nuovo dispositivo:"; +"user_verification_session_details_additional_information_untrusted_other_user" = "Fino a quando questo utente non si fida di questo dispositivo, i messaggi inviati da e verso di esso sono marcati da avvisi. In alternativa, puoi verificarlo manualmente."; +"user_verification_session_details_additional_information_untrusted_current_user" = "Se non hai fatto l'accesso a questa sessione, il tuo account potrebbe essere compromesso."; +"user_verification_session_details_verify_action_current_user" = "Verifica"; +"user_verification_session_details_verify_action_other_user" = "Verifica manualmente"; diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index f3312f925..431ee92d6 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -562,3 +562,9 @@ "room_resource_limit_exceeded_message_contact_1" = " Please "; "settings_ui_theme_black" = "Black"; "settings_flair" = "特色を表示する"; +// String for App Store +"store_short_description" = "セキュアな分散型チャット/VoIP"; +"close" = "閉じる"; +// Accessibility +"accessibility_checkbox_label" = "チェックボックス"; +"auth_login_single_sign_on" = "シングルサインオン(SSO)でサインイン"; diff --git a/Riot/Assets/sq.lproj/Localizable.strings b/Riot/Assets/sq.lproj/Localizable.strings index d060cfcd8..40a5a18eb 100644 --- a/Riot/Assets/sq.lproj/Localizable.strings +++ b/Riot/Assets/sq.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ në %@"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ dërgoi një ngjitës"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ dëshiron të verifikojë"; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 9918d92a0..573461056 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -602,18 +602,18 @@ "key_backup_recover_title" = "Mesazhe të Sigurt"; "key_backup_recover_empty_backup_title" = "Kopjeruajtje e zbrazët"; "key_backup_recover_empty_backup_message" = "S’ka kyç për rikthim"; -"key_backup_recover_from_passphrase_info" = "Që të shkyçni historikun e mesazheve tuaj të sigurt përdorni frazëkalimin tuaj të rikthimeve"; +"key_backup_recover_from_passphrase_info" = "Që të shkyçni historikun e mesazheve tuaj të sigurt përdorni frazëkalimin tuaj të rimarrjeve"; "key_backup_recover_from_passphrase_passphrase_title" = "Jepeni"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "Jepni Frazëkalimin"; "key_backup_recover_from_passphrase_recover_action" = "Shkyçeni Historikun"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Nuk e dini frazëkalimin tuaj të rikthimeve? Mundeni të "; -"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "përdorni kyçin tuaj të rikthimeve"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Nuk e dini frazëkalimin tuaj të rimarrjeve? Mundeni të "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "përdorni kyçin tuaj të rimarrjeve"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; -"key_backup_recover_from_recovery_key_info" = "Përdorni kyçin tuaj të rikthimeve për të shkyçur historikun tuaj të mesazheve të sigurt"; +"key_backup_recover_from_recovery_key_info" = "Që të shkyçni historikun e mesazheve tuaj të sigurt, përdorni frazëkalimin tuaj të rimarrjeve"; "key_backup_recover_from_recovery_key_recovery_key_title" = "Jepeni"; -"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Jepni Kyç Rikthimi"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Jepni Kyç Rimarrjesh"; "key_backup_recover_from_recovery_key_recover_action" = "Shkyçe Historikun"; -"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Humbët kyçin tuaj të rikthimeve? Te rregullimet mund të caktoni një të ri."; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Humbët kyçin tuaj të rimarrjeve? Te rregullimet mund të caktoni një të ri."; "key_backup_recover_success_info" = "Kopjeruajtja u Rikthye!"; "key_backup_recover_done_action" = "U bë"; "key_backup_setup_banner_title_part1" = "Rregulloni Rikthim Mesazhesh të Sigurt"; @@ -626,22 +626,22 @@ "key_backup_setup_intro_setup_action_without_existing_backup" = "Fillo të përdorësh Kopjeruajtje Kyçesh"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Përdor Kopjeruajtje Kyçesh"; "key_backup_setup_passphrase_title" = "Sigurojeni kopjeruajtjen tuaj me një Frazëkalim"; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Ose, sigurojeni kopjeruajtjen tuaj me një Kyç Rikthimesh, duke e ruajtur këtë diku të parrezikuar."; -"key_backup_setup_passphrase_setup_recovery_key_action" = "(Të mëtejshme) Rregullojeni me një Kyç Rikthimesh"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Ose, sigurojeni kopjeruajtjen tuaj me një Kyç Rimarrjesh, duke e ruajtur këtë diku të parrezikuar."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Të mëtejshme) Rregullojeni me një Kyç Rimarrjesh"; "key_backup_setup_success_title" = "Sukses!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nKyçi juaj i rikthimeve është një lloj rrjeti sigurie - mund ta përdorni për të rifituar hyrje te mesazhet tuaj të fshehtëzuar, nëse harroni frazëkalimin tuaj.\n\nMbajeni kyçin tuaj të rikthimeve diku shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë)."; -"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Ruani Kyç Rikthimesh"; +"key_backup_setup_success_from_passphrase_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nKyçi juaj i rimarrjeve është një lloj mase sigurie - mund ta përdorni për të rifituar hyrje te mesazhet tuaj të fshehtëzuar, nëse harroni frazëkalimin tuaj.\n\nMbajeni kyçin tuaj të rimarrjeve diku shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Ruani Kyç Rimarrjesh"; "key_backup_setup_success_from_passphrase_done_action" = "U krye"; // Success from recovery key -"key_backup_setup_success_from_recovery_key_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nBëni një kopje të këtij kyçi rikthimesh dhe mbajeni të parrezikuar."; -"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Kyç Rikthimesh"; +"key_backup_setup_success_from_recovery_key_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nBëni një kopje të këtij kyçi rimarrjesh dhe mbajeni të parrezikuar."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Kyç Rimarrjesh"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Bëni një Kopje"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Kam bërë një kopje"; -"key_backup_recover_invalid_passphrase_title" = "Frazëkalim Rikthimi i Pasaktë"; -"key_backup_recover_invalid_passphrase" = "S’u shfshehtëzua dot kopjeruajtja me këtë frazëkalim: ju lutemi, verifikoni që dhatë frazëkalimin e duhur të rikthimeve."; -"key_backup_recover_invalid_recovery_key_title" = "Mospërputhje Kyçesh Rikthimi"; -"key_backup_recover_invalid_recovery_key" = "Nuk u shfshehtëzua dot kopjeruajtja me këtë kyç: ju lutemi, verifikoni që dhatë kyçin e duhur të rikthimeve."; +"key_backup_recover_invalid_passphrase_title" = "Frazëkalim Rimarrjeje i Pasaktë"; +"key_backup_recover_invalid_passphrase" = "S’u shfshehtëzua dot kopjeruajtja me këtë frazëkalim: ju lutemi, verifikoni që dhatë frazëkalimin e duhur të rimarrjeve."; +"key_backup_recover_invalid_recovery_key_title" = "Mospërputhje Kyçesh Rimarrjeje"; +"key_backup_recover_invalid_recovery_key" = "Nuk u shfshehtëzua dot kopjeruajtja me këtë kyç: ju lutemi, verifikoni që dhatë kyçin e duhur të rimarrjeve."; "key_backup_setup_banner_title" = "Mos humbni kurrë mesazhe të fshehtëzuar"; "key_backup_setup_banner_subtitle" = "Fillo të përdorësh Kopjeruajtje Kyçesh"; "key_backup_recover_banner_title" = "Mos humbni kurrë mesazhe të fshehtëzuar"; @@ -933,3 +933,21 @@ "room_widget_permission_widget_id_permission" = "ID Widget-i"; "room_widget_permission_room_id_permission" = "ID Dhome"; "service_terms_modal_policy_checkbox_accessibility_hint" = "I vini shenjë që të pranohet %@"; +"settings_labs_dm_key_verification" = "Verifikim kyçesh përmes mesazhi të drejtpërdrejtë"; +"settings_labs_cross_signing" = "Nënshkrim kryq"; +"settings_labs" = "LABS"; +"settings_calls_stun_server_fallback_button" = "Lejoni shërbyes rrugëzgjidhje asistimi thirrjesh"; +"settings_calls_stun_server_fallback_description" = "Lejoni shërbyes rrugëzgjidhje asistimi thirrjesh %@, kur shërbyesi juaj Home nuk ofron një të tillë (gjatë thirrjes, adresa juaj IP do t’i bëhet e ditur)"; +"key_verification_tile_request_incoming_title" = "Kërkesë verifikimi"; +"key_verification_tile_request_outgoing_title" = "Email-i i verifikimit u dërgua"; +"key_verification_tile_request_status_data_loading" = "Ngarkim të dhënat…"; +"key_verification_tile_request_status_waiting" = "Në pritje…"; +"key_verification_tile_request_status_expired" = "I skaduar"; +"key_verification_tile_request_status_cancelled_by_me" = "Anuluat"; +"key_verification_tile_request_status_cancelled" = "%@ u anulua"; +"key_verification_tile_request_status_accepted" = "Pranuat"; +"key_verification_tile_request_incoming_approval_accept" = "Pranoje"; +"key_verification_tile_request_incoming_approval_decline" = "Hidhe poshtë"; +"key_verification_tile_conclusion_done_title" = "I verifikuar"; +"key_verification_tile_conclusion_warning_title" = "Hyrje jo e besuar në"; +"key_verification_incoming_request_incoming_alert_message" = "%s dëshiron të verifikojë"; diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index 6499f7712..37848b595 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1376,6 +1376,55 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +
  • + DGCollectionViewLeftAlignFlowLayout (https://github.com/Digipolitan/collection-view-left-align-flow-layout) +

    This is a simple layout that align does not try to fulfill the lines but stick elements to the left. +

    DGCollectionViewLeftAlignFlowLayout is licensed under the BSD 3-Clause license. +
    Copyright (c) 2017, Digipolitan All rights reserved. +

    Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: +

    - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +

    - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +

    - Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +

    +
  • +
  • + KTCenterFlowLayout (https://github.com/keighl/KTCenterFlowLayout) +

    KTCenterFlowLayout is a subclass of UICollectionViewFlowLayout which Aligns cells to the center of a collection view. +

    It is released under the MIT license. +

    Copyright (c) keighl (http://github.com/keighl) +

    Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: +

    The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. +

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +

    +
  • diff --git a/Riot/Assets/uk.lproj/InfoPlist.strings b/Riot/Assets/uk.lproj/InfoPlist.strings index e215f2935..cf6bb7c59 100644 --- a/Riot/Assets/uk.lproj/InfoPlist.strings +++ b/Riot/Assets/uk.lproj/InfoPlist.strings @@ -2,4 +2,5 @@ "NSCameraUsageDescription" = "Камера використовується для знімків фото і відео, а також для відео-викликів."; "NSPhotoLibraryUsageDescription" = "Фотографії використовуються для надсилання фото і відео."; "NSMicrophoneUsageDescription" = "Мікрофон використовується для відео і викликів."; -"NSContactsUsageDescription" = "Щоб показати, які з ваших контактів вже використовують Riot чи Matrix, ми можемо надіслати адреси електронної пошти і номери телефонів з вашої адресної книги до вашого сервера ідентифікації Matrix. Новий Vector не зберігає і не використовує ці дані. Для докладних відомостей ознайомтеся зі сторінкою політики приватності в налаштуваннях додатку."; +"NSContactsUsageDescription" = "Щоб показати, які з ваших контактів вже використовують Matrix, Riot може надіслати адреси електронної пошти і номери телефонів з вашої адресної книги до вашого ідентифікаційного сервера Matrix. При наявності підтримки, перед надсиланням створюється хеш особистих даних. Для докладних відомостей ознайомтеся з політикою приватності свого ідентифікаційного сервера."; +"NSCalendarsUsageDescription" = "Переглядайте свої заплановані зустрічі в додатку."; diff --git a/Riot/Assets/uk.lproj/Localizable.strings b/Riot/Assets/uk.lproj/Localizable.strings index f3fe81265..272ade2ee 100644 --- a/Riot/Assets/uk.lproj/Localizable.strings +++ b/Riot/Assets/uk.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Повідомлення від %@"; +"MSG_FROM_USER" = "%@ надсилає повідомлення"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ пише в %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ надсилає вам зображення %@"; +"IMAGE_FROM_USER" = "%@ надсилає зображення %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ оприлюднює зображення %@ в %@"; /* A single unread message in a room */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Груповий виклик від %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Груповий відео-виклик від %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ в %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ надсилає наліпку"; diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 2a4ce162c..384289cbd 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -52,7 +52,7 @@ "auth_repeat_password_placeholder" = "Повторіть пароль"; "auth_repeat_new_password_placeholder" = "Підтвердьте новий пароль"; "auth_home_server_placeholder" = "URL (наприклад, https://matrix.org)"; -"auth_identity_server_placeholder" = "URL (наприклад, https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (наприклад, https://vector.im)"; "auth_invalid_login_param" = "Неправильне ім'я користувача або пароль"; "auth_invalid_user_name" = "Імена користувачів можуть містити лише літери, цифри, крапки, дефіси й підкреслення"; "auth_invalid_password" = "Пароль надто короткий (мінімум 6 знаків)"; @@ -81,3 +81,55 @@ "auth_recaptcha_message" = "Цей домашній сервер бажає переконатися, що ви не робот"; "auth_reset_password_message" = "Щоб відновити пароль, введіть адресу е-пошти, пов'язану з вашим обліковим записом:"; "auth_reset_password_missing_email" = "Необхідно ввести адресу е-пошти, пов'язану з вашим обліковим записом."; +// String for App Store +"store_short_description" = "Захищений, децентралізований чат/VoIP"; +"close" = "Закрити"; +// Accessibility +"accessibility_checkbox_label" = "прапорець"; +"auth_add_email_message_2" = "Вкажіть е-пошту для відновлення облікового запису, а також для можливості знаходження вас іншими користувачами."; +"auth_add_phone_message_2" = "Вкажіть номер телефону для можливості знаходження вас іншими користувачами."; +"auth_add_email_phone_message_2" = "Вкажіть е-пошту для відновлення облікового запису. Використовуйте електронну пошту чи номер телефону для можливості знаходження вас іншими користувачами."; +"auth_email_is_required" = "Ідентифікаційний сервер не налаштовано, тому ви не можете додати адресу електронної пошти, щоб мати можливість відновити пароль в майбутньому."; +"auth_phone_is_required" = "Ідентифікаційний сервер не налаштовано, тому ви не можете додати номер телефону, щоб мати можливість відновити пароль в майбутньому."; +"auth_forgot_password_error_no_configured_identity_server" = "Ідентифікаційний сервер не налаштовано: додайте його, щоб мати можливість відновити пароль в майбутньому."; +"auth_reset_password_missing_password" = "Необхідно ввести новий пароль."; +"auth_reset_password_email_validation_message" = "На адресу %@ надіслано лист. Після переходу за посиланням в листі, натисніть внизу."; +"auth_reset_password_next_step_button" = "Я підтверджую свою адресу е-пошти"; +"auth_reset_password_error_unauthorized" = "Не вдалося перевірити е-пошту: переконайтеся, що ви перейшли за посиланням у листі"; +"auth_reset_password_error_not_found" = "Схоже, ваша адреса електронної пошти не пов'язана з жодним Matrix ID на цьому домашньому сервері."; +"auth_reset_password_error_is_required" = "Ідентифікаційний сервер не налаштовано: додайте його в параметрах сервера, щоб мати можливість відновити пароль в майбутньому."; +"auth_reset_password_success_message" = "Ваш пароль було відновлено.\n\nСеанс входу завершено на всіх пристроях і припинено отримання push-сповіщень. Щоб активувати сповіщення, виконайте вхід з новим паролем на кожному пристрої."; +"auth_add_email_and_phone_warning" = "Реєстрація за допомогою електронної пошти і номера телефону водночас не підтримується, доки немає API. Лише номер телефону буде додано до облікового запису. Ви можете додати електронну пошту до свого профілю в налаштуваннях."; +"auth_accept_policies" = "Ознайомтесь та прийміть правила цього домашнього сервера:"; +"auth_softlogout_signed_out" = "Ви вийшли"; +"auth_softlogout_sign_in" = "Увійти"; +"auth_softlogout_reason" = "Адміністратор вашого домашнього сервера (%1$@) завершив сеанс вашого облікового запису %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Увійдіть для відновлення ключів шифрування, що зберігаються тільки на цьому пристрої. Вам вони необхідні для прочитання всіх ваших захищених повідомлень на будь-якому пристрої."; +"auth_softlogout_clear_data" = "Стерти особисті дані"; +"auth_softlogout_clear_data_message_1" = "Попередження: Ваші особисті дані (включно з ключами шифрування) все ще зберігаються на цьому пристрої."; +"auth_softlogout_clear_data_message_2" = "Зітріть, якщо ви припинили користуватися цим пристроєм, або хочете увійти в інший обліковий запис."; +"auth_softlogout_clear_data_button" = "Стерти всі дані"; +"auth_softlogout_clear_data_sign_out_title" = "Ви впевнені?"; +"auth_softlogout_clear_data_sign_out_msg" = "Ви справді хочете стерти всі дані, що зберігаються на цьому пристрої? Увійдіть знову для доступу до даних і повідомлень свого облікового запису."; +"auth_softlogout_clear_data_sign_out" = "Вийти"; +"room_resource_limit_exceeded_message_contact_1" = " Будь ласка, "; +// Generic errors +"error_invite_3pid_with_no_identity_server" = "Додайте ідентифікаційний сервер в параметрах, щоб запросити е-поштою."; +"error_not_supported_on_mobile" = "Ви не можете робити це з %@ мобільного."; +// Errors +"error_user_already_logged_in" = "Схоже, ви намагаєтесь з'єднатися до іншого домашнього сервера. Хочете вийти?"; +// Chat creation +"room_creation_title" = "Новий чат"; +"room_creation_account" = "Обліковий запис"; +"room_creation_appearance" = "Зовнішній вигляд"; +"room_creation_appearance_name" = "Назва"; +"room_creation_appearance_picture" = "Зображення чату (необов'язково)"; +"room_creation_privacy" = "Приватність"; +"room_creation_private_room" = "Цей чат є приватним"; +"room_creation_public_room" = "Цей чат є публічним"; +"room_creation_make_public" = "Зробити публічним"; +"room_creation_make_public_prompt_title" = "Зробити цей чат публічним?"; +"room_creation_make_public_prompt_msg" = "Ви справді хочете зробити цей чат публічним? Будь-хто зможе долучатися й читати повідомлення."; +"room_creation_keep_private" = "Залишити приватним"; +"room_creation_make_private" = "Зробити приватним"; +"room_creation_wait_for_creation" = "Триває створення кімнати. Будь ласка, зачекайте."; diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h index ca82723c6..ce1282065 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h @@ -42,6 +42,20 @@ extern NSString *const kMXKRoomBubbleCellLongPressOnReactionView; */ extern NSString *const kMXKRoomBubbleCellEventIdKey; +/** + Action identifier used when the user pressed accept button for an incoming key verification request. + + The `userInfo` dictionary contains an `NSString` object under the `kMXKRoomBubbleCellEventIdKey` key, representing the event id associated to the key verification request. + */ +extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed; + +/** + Action identifier used when the user pressed decline button for an incoming key verification request. + + The `userInfo` dictionary contains an `NSString` object under the `kMXKRoomBubbleCellEventIdKey` key, representing the event id associated to the key verification request. + */ +extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed; + /** Define a `MXKRoomBubbleTableViewCell` category at Riot level to handle bubble customisation. */ diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index 077bfe8b1..6723f2dc0 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -31,6 +31,8 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellTapOnReceiptsContainer"; NSString *const kMXKRoomBubbleCellLongPressOnReactionView = @"kMXKRoomBubbleCellLongPressOnReactionView"; NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey"; +NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed = @"kMXKRoomBubbleCellKeyVerificationAcceptPressed"; +NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = @"kMXKRoomBubbleCellKeyVerificationDeclinePressed"; @implementation MXKRoomBubbleTableViewCell (Riot) @@ -76,6 +78,12 @@ NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey"; viewTag:(NSInteger)viewTag displayOnLeft:(BOOL)displayOnLeft { + if (!self.bubbleInfoContainer) + { + NSLog(@"[MXKRoomBubbleTableViewCell+Riot] bubbleInfoContainer property is missing for cell class: %@", NSStringFromClass(self.class)); + return; + } + NSArray *bubbleComponents = bubbleData.bubbleComponents; MXKRoomBubbleComponent *component = bubbleComponents[componentIndex]; diff --git a/Riot/Categories/MXRoom+Riot.h b/Riot/Categories/MXRoom+Riot.h index 4da7fa074..9d02bbc2d 100644 --- a/Riot/Categories/MXRoom+Riot.h +++ b/Riot/Categories/MXRoom+Riot.h @@ -17,6 +17,8 @@ #import +#import "UserEncryptionTrustLevel.h" + /** Define a `MXRoom` category at Riot level. */ @@ -75,4 +77,11 @@ */ - (void)allMessages:(void (^)(void))completion; +/** + Get user encryption trust level. + + @param userId The user id. + */ +- (UserEncryptionTrustLevel)encryptionTrustLevelForUserId:(NSString*)userId; + @end diff --git a/Riot/Categories/MXRoom+Riot.m b/Riot/Categories/MXRoom+Riot.m index cc43e075a..cbd520688 100644 --- a/Riot/Categories/MXRoom+Riot.m +++ b/Riot/Categories/MXRoom+Riot.m @@ -324,6 +324,37 @@ } } +- (UserEncryptionTrustLevel)encryptionTrustLevelForUserId:(NSString*)userId +{ + UserEncryptionTrustLevel userEncryptionTrustLevel; + + if (self.mxSession.crypto) + { + MXUsersTrustLevelSummary *usersTrustLevelSummary = [self.mxSession.crypto trustLevelSummaryForUserIds:@[userId]]; + + double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; + + if (trustedDevicesPercentage >= 1.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted; + } + else if (trustedDevicesPercentage == 0.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelNormal; + } + else + { + userEncryptionTrustLevel = UserEncryptionTrustLevelWarning; + } + } + else + { + userEncryptionTrustLevel = UserEncryptionTrustLevelNone; + } + + return userEncryptionTrustLevel; +} + #pragma mark - - (MXPushRule*)getRoomPushRule diff --git a/Riot/Categories/MXRoomSummary+Riot.h b/Riot/Categories/MXRoomSummary+Riot.h index 804a6a6be..c69617bf5 100644 --- a/Riot/Categories/MXRoomSummary+Riot.h +++ b/Riot/Categories/MXRoomSummary+Riot.h @@ -16,6 +16,17 @@ #import +/** + RoomEncryptionTrustLevel represents the trust level in an encrypted room. + */ +typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) { + RoomEncryptionTrustLevelTrusted, + RoomEncryptionTrustLevelWarning, + RoomEncryptionTrustLevelNormal, + RoomEncryptionTrustLevelUnknown +}; + + /** Define a `MXRoomSummary` category at Riot level. */ @@ -32,4 +43,11 @@ */ - (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView; +/** + Get the trust level in the room. + + @return the trust level. + */ +- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel; + @end diff --git a/Riot/Categories/MXRoomSummary+Riot.m b/Riot/Categories/MXRoomSummary+Riot.m index 0367f2436..688f9d2c8 100644 --- a/Riot/Categories/MXRoomSummary+Riot.m +++ b/Riot/Categories/MXRoomSummary+Riot.m @@ -47,4 +47,34 @@ mxkImageView.contentMode = UIViewContentModeScaleAspectFill; } +- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + RoomEncryptionTrustLevel roomEncryptionTrustLevel = RoomEncryptionTrustLevelUnknown; + if (self.trust) + { + double trustedUsersPercentage = self.trust.trustedUsersProgress.fractionCompleted; + double trustedDevicesPercentage = self.trust.trustedDevicesProgress.fractionCompleted; + + if (trustedUsersPercentage >= 1.0) + { + if (trustedDevicesPercentage >= 1.0) + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; + } + else + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning; + } + } + else + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; + } + + roomEncryptionTrustLevel = roomEncryptionTrustLevel; + } + + return roomEncryptionTrustLevel; +} + @end diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m index 3d8805b5e..0f2f9782d 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -17,6 +17,7 @@ #import "MXSession+Riot.h" #import "MXRoom+Riot.h" +#import "Riot-Swift.h" @implementation MXSession (Riot) diff --git a/Riot/Categories/UIStackView.swift b/Riot/Categories/UIStackView.swift index e1ebb800b..b3040c916 100644 --- a/Riot/Categories/UIStackView.swift +++ b/Riot/Categories/UIStackView.swift @@ -18,7 +18,7 @@ import UIKit extension UIStackView { - func vc_removeAllSubviews() { + func vc_removeAllArrangedSubviews() { let subviews = self.arrangedSubviews for subview in subviews { self.removeArrangedSubview(subview) diff --git a/Riot/Categories/UIView.swift b/Riot/Categories/UIView.swift index 5a4652104..e455766e1 100644 --- a/Riot/Categories/UIView.swift +++ b/Riot/Categories/UIView.swift @@ -19,7 +19,7 @@ import Foundation extension UIView { /// Add a subview matching parent view using autolayout - func vc_addSubViewMatchingParent(_ subView: UIView) { + @objc func vc_addSubViewMatchingParent(_ subView: UIView) { self.addSubview(subView) subView.translatesAutoresizingMaskIntoConstraints = false let views = ["view": subView] @@ -31,4 +31,10 @@ extension UIView { constraints.forEach { $0.isActive = true } } } + + @objc func vc_removeAllSubviews() { + for subView in self.subviews { + subView.removeFromSuperview() + } + } } diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 5daa4047a..45d21e3f9 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -31,6 +31,7 @@ internal enum Asset { internal static let riotIconCallkit = ImageAsset(name: "riot_icon_callkit") internal static let adminIcon = ImageAsset(name: "admin_icon") internal static let backIcon = ImageAsset(name: "back_icon") + internal static let camera = ImageAsset(name: "camera") internal static let chevron = ImageAsset(name: "chevron") internal static let closeButton = ImageAsset(name: "close_button") internal static let disclosureIcon = ImageAsset(name: "disclosure_icon") @@ -50,6 +51,9 @@ internal enum Asset { internal static let e2eBlocked = ImageAsset(name: "e2e_blocked") internal static let e2eUnencrypted = ImageAsset(name: "e2e_unencrypted") internal static let e2eWarning = ImageAsset(name: "e2e_warning") + internal static let encryptionNormal = ImageAsset(name: "encryption_normal") + internal static let encryptionTrusted = ImageAsset(name: "encryption_trusted") + internal static let encryptionWarning = ImageAsset(name: "encryption_warning") internal static let directChatOff = ImageAsset(name: "directChatOff") internal static let directChatOn = ImageAsset(name: "directChatOn") internal static let favourite = ImageAsset(name: "favourite") @@ -64,6 +68,7 @@ internal enum Asset { internal static let importFilesButton = ImageAsset(name: "import_files_button") internal static let keyBackupLogo = ImageAsset(name: "key_backup_logo") internal static let revealPasswordButton = ImageAsset(name: "reveal_password_button") + internal static let keyVerificationSuccessShield = ImageAsset(name: "key_verification_success_shield") internal static let launchScreenRiot = ImageAsset(name: "LaunchScreenRiot") internal static let cameraCapture = ImageAsset(name: "camera_capture") internal static let cameraPlay = ImageAsset(name: "camera_play") diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 7ec5424ed..a756e2933 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -12,11 +12,6 @@ import UIKit // swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name internal enum StoryboardScene { - internal enum DeviceVerificationDataLoadingViewController: StoryboardType { - internal static let storyboardName = "DeviceVerificationDataLoadingViewController" - - internal static let initialScene = InitialSceneType(storyboard: DeviceVerificationDataLoadingViewController.self) - } internal enum DeviceVerificationIncomingViewController: StoryboardType { internal static let storyboardName = "DeviceVerificationIncomingViewController" @@ -27,16 +22,6 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: DeviceVerificationStartViewController.self) } - internal enum DeviceVerificationVerifiedViewController: StoryboardType { - internal static let storyboardName = "DeviceVerificationVerifiedViewController" - - internal static let initialScene = InitialSceneType(storyboard: DeviceVerificationVerifiedViewController.self) - } - internal enum DeviceVerificationVerifyViewController: StoryboardType { - internal static let storyboardName = "DeviceVerificationVerifyViewController" - - internal static let initialScene = InitialSceneType(storyboard: DeviceVerificationVerifyViewController.self) - } internal enum EditHistoryViewController: StoryboardType { internal static let storyboardName = "EditHistoryViewController" @@ -52,6 +37,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupRecoverFromPassphraseViewController.self) } + internal enum KeyBackupRecoverFromPrivateKeyViewController: StoryboardType { + internal static let storyboardName = "KeyBackupRecoverFromPrivateKeyViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyBackupRecoverFromPrivateKeyViewController.self) + } internal enum KeyBackupRecoverFromRecoveryKeyViewController: StoryboardType { internal static let storyboardName = "KeyBackupRecoverFromRecoveryKeyViewController" @@ -82,6 +72,46 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupSetupSuccessFromRecoveryKeyViewController.self) } + internal enum KeyVerificationDataLoadingViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationDataLoadingViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationDataLoadingViewController.self) + } + internal enum KeyVerificationScanConfirmationViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationScanConfirmationViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationScanConfirmationViewController.self) + } + internal enum KeyVerificationSelfVerifyStartViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationSelfVerifyStartViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationSelfVerifyStartViewController.self) + } + internal enum KeyVerificationSelfVerifyWaitViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationSelfVerifyWaitViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationSelfVerifyWaitViewController.self) + } + internal enum KeyVerificationVerifiedViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationVerifiedViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationVerifiedViewController.self) + } + internal enum KeyVerificationVerifyBySASViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationVerifyBySASViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationVerifyBySASViewController.self) + } + internal enum KeyVerificationVerifyByScanningViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationVerifyByScanningViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationVerifyByScanningViewController.self) + } + internal enum QRCodeReaderViewController: StoryboardType { + internal static let storyboardName = "QRCodeReaderViewController" + + internal static let initialScene = InitialSceneType(storyboard: QRCodeReaderViewController.self) + } internal enum ReactionHistoryViewController: StoryboardType { internal static let storyboardName = "ReactionHistoryViewController" @@ -117,6 +147,21 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: TemplateScreenViewController.self) } + internal enum UserVerificationSessionStatusViewController: StoryboardType { + internal static let storyboardName = "UserVerificationSessionStatusViewController" + + internal static let initialScene = InitialSceneType(storyboard: UserVerificationSessionStatusViewController.self) + } + internal enum UserVerificationSessionsStatusViewController: StoryboardType { + internal static let storyboardName = "UserVerificationSessionsStatusViewController" + + internal static let initialScene = InitialSceneType(storyboard: UserVerificationSessionsStatusViewController.self) + } + internal enum UserVerificationStartViewController: StoryboardType { + internal static let storyboardName = "UserVerificationStartViewController" + + internal static let initialScene = InitialSceneType(storyboard: UserVerificationStartViewController.self) + } internal enum WidgetPermissionViewController: StoryboardType { internal static let storyboardName = "WidgetPermissionViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 305d37a19..cb57cfe8a 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -226,7 +226,7 @@ internal enum VectorL10n { internal static var authResetPasswordNextStepButton: String { return VectorL10n.tr("Vector", "auth_reset_password_next_step_button") } - /// Your password has been reset.\n\nYou have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, re-log in on each device. + /// Your password has been reset.\n\nYou have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, re-log in on each device. internal static var authResetPasswordSuccessMessage: String { return VectorL10n.tr("Vector", "auth_reset_password_success_message") } @@ -362,7 +362,7 @@ internal enum VectorL10n { internal static var callAlreadyDisplayed: String { return VectorL10n.tr("Vector", "call_already_displayed") } - /// Incoming video call... + /// Incoming video call… internal static var callIncomingVideo: String { return VectorL10n.tr("Vector", "call_incoming_video") } @@ -370,7 +370,7 @@ internal enum VectorL10n { internal static func callIncomingVideoPrompt(_ p1: String) -> String { return VectorL10n.tr("Vector", "call_incoming_video_prompt", p1) } - /// Incoming call... + /// Incoming call… internal static var callIncomingVoice: String { return VectorL10n.tr("Vector", "call_incoming_voice") } @@ -778,15 +778,15 @@ internal enum VectorL10n { internal static var deviceVerificationEmojiUnicorn: String { return VectorL10n.tr("Vector", "device_verification_emoji_unicorn") } - /// Cannot load device information. + /// Cannot load session information. internal static var deviceVerificationErrorCannotLoadDevice: String { return VectorL10n.tr("Vector", "device_verification_error_cannot_load_device") } - /// Verify this device to mark it as trusted. Trusting devices of partners gives you extra peace of mind when using end-to-end encrypted messages. + /// Verify this session to mark it as trusted. Trusting sessions of partners gives you extra peace of mind when using end-to-end encrypted messages. internal static var deviceVerificationIncomingDescription1: String { return VectorL10n.tr("Vector", "device_verification_incoming_description_1") } - /// Verifying this device will mark it as trusted, and also mark your device as trusted to the partner. + /// Verifying this session will mark it as trusted, and also mark your session as trusted to the partner. internal static var deviceVerificationIncomingDescription2: String { return VectorL10n.tr("Vector", "device_verification_incoming_description_2") } @@ -798,6 +798,42 @@ internal enum VectorL10n { internal static var deviceVerificationSecurityAdvice: String { return VectorL10n.tr("Vector", "device_verification_security_advice") } + /// This wasn't me + internal static var deviceVerificationSelfVerifyAlertCancelAction: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_alert_cancel_action") + } + /// Use this session to verify your new one, granting it access to encrypted messages: %@\nIf you didn’t sign in to this session, your account may be compromised. + internal static func deviceVerificationSelfVerifyAlertMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "device_verification_self_verify_alert_message", p1) + } + /// New Sign In + internal static var deviceVerificationSelfVerifyAlertTitle: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_alert_title") + } + /// Use this session to verify your new one, granting it access to encrypted messages. + internal static var deviceVerificationSelfVerifyStartInformation: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_start_information") + } + /// Start verification + internal static var deviceVerificationSelfVerifyStartVerifyAction: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_start_verify_action") + } + /// Waiting… + internal static var deviceVerificationSelfVerifyStartWaiting: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_start_waiting") + } + /// Use an existing session to verify this new one, granting it access to encrypted messages. + internal static var deviceVerificationSelfVerifyWaitInformation: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_wait_information") + } + /// Complete security + internal static var deviceVerificationSelfVerifyWaitTitle: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_wait_title") + } + /// Waiting… + internal static var deviceVerificationSelfVerifyWaitWaiting: String { + return VectorL10n.tr("Vector", "device_verification_self_verify_wait_waiting") + } /// Verify by comparing a short text string internal static var deviceVerificationStartTitle: String { return VectorL10n.tr("Vector", "device_verification_start_title") @@ -814,15 +850,15 @@ internal enum VectorL10n { internal static var deviceVerificationStartVerifyButton: String { return VectorL10n.tr("Vector", "device_verification_start_verify_button") } - /// Waiting for partner to accept... + /// Waiting for partner to accept… internal static var deviceVerificationStartWaitPartner: String { return VectorL10n.tr("Vector", "device_verification_start_wait_partner") } - /// Verify device + /// Verify session internal static var deviceVerificationTitle: String { return VectorL10n.tr("Vector", "device_verification_title") } - /// You've successfully verified this device. + /// You've successfully verified this session. internal static var deviceVerificationVerifiedDescription1: String { return VectorL10n.tr("Vector", "device_verification_verified_description_1") } @@ -838,15 +874,15 @@ internal enum VectorL10n { internal static var deviceVerificationVerifiedTitle: String { return VectorL10n.tr("Vector", "device_verification_verified_title") } - /// Verify this device by confirming the following emoji appear on the screen of the partner + /// Verify this session by confirming the following emoji appear on the screen of the partner internal static var deviceVerificationVerifyTitleEmoji: String { return VectorL10n.tr("Vector", "device_verification_verify_title_emoji") } - /// Verify this device by confirming the following numbers appear on the screen of the partner + /// Verify this session by confirming the following numbers appear on the screen of the partner internal static var deviceVerificationVerifyTitleNumber: String { return VectorL10n.tr("Vector", "device_verification_verify_title_number") } - /// Waiting for partner to confirm... + /// Waiting for partner to confirm… internal static var deviceVerificationVerifyWaitPartner: String { return VectorL10n.tr("Vector", "device_verification_verify_wait_partner") } @@ -926,7 +962,7 @@ internal enum VectorL10n { internal static var e2eKeyBackupWrongVersionTitle: String { return VectorL10n.tr("Vector", "e2e_key_backup_wrong_version_title") } - /// You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver.\nThis is a once off; sorry for the inconvenience. + /// You need to log back in to generate end-to-end encryption keys for this session and submit the public key to your homeserver.\nThis is a once off; sorry for the inconvenience. internal static var e2eNeedLogInAgain: String { return VectorL10n.tr("Vector", "e2e_need_log_in_again") } @@ -934,11 +970,11 @@ internal enum VectorL10n { internal static var e2eRoomKeyRequestIgnoreRequest: String { return VectorL10n.tr("Vector", "e2e_room_key_request_ignore_request") } - /// Your unverified device '%@' is requesting encryption keys. + /// Your unverified session '%@' is requesting encryption keys. internal static func e2eRoomKeyRequestMessage(_ p1: String) -> String { return VectorL10n.tr("Vector", "e2e_room_key_request_message", p1) } - /// You added a new device '%@', which is requesting encryption keys. + /// You added a new session '%@', which is requesting encryption keys. internal static func e2eRoomKeyRequestMessageNewDevice(_ p1: String) -> String { return VectorL10n.tr("Vector", "e2e_room_key_request_message_new_device", p1) } @@ -946,7 +982,7 @@ internal enum VectorL10n { internal static var e2eRoomKeyRequestShareWithoutVerifying: String { return VectorL10n.tr("Vector", "e2e_room_key_request_share_without_verifying") } - /// Start verification... + /// Start verification… internal static var e2eRoomKeyRequestStartVerification: String { return VectorL10n.tr("Vector", "e2e_room_key_request_start_verification") } @@ -1030,7 +1066,7 @@ internal enum VectorL10n { internal static var eventFormatterRerequestKeysPart1Link: String { return VectorL10n.tr("Vector", "event_formatter_rerequest_keys_part1_link") } - /// from your other devices. + /// from your other sessions. internal static var eventFormatterRerequestKeysPart2: String { return VectorL10n.tr("Vector", "event_formatter_rerequest_keys_part2") } @@ -1258,7 +1294,7 @@ internal enum VectorL10n { internal static var keyBackupRecoverBannerTitle: String { return VectorL10n.tr("Vector", "key_backup_recover_banner_title") } - /// Connect this device to Key Backup + /// Connect this session to Key Backup internal static var keyBackupRecoverConnentBannerSubtitle: String { return VectorL10n.tr("Vector", "key_backup_recover_connent_banner_subtitle") } @@ -1294,6 +1330,10 @@ internal enum VectorL10n { internal static var keyBackupRecoverFromPassphraseRecoverAction: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_recover_action") } + /// Restoring backup… + internal static var keyBackupRecoverFromPrivateKeyInfo: String { + return VectorL10n.tr("Vector", "key_backup_recover_from_private_key_info") + } /// Use your recovery key to unlock your secure message history internal static var keyBackupRecoverFromRecoveryKeyInfo: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_info") @@ -1470,6 +1510,138 @@ internal enum VectorL10n { internal static var keyBackupSetupTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_title") } + /// You have to bootstrap cross-signing + internal static var keyVerificationBootstrapNotSetupMessage: String { + return VectorL10n.tr("Vector", "key_verification_bootstrap_not_setup_message") + } + /// Error + internal static var keyVerificationBootstrapNotSetupTitle: String { + return VectorL10n.tr("Vector", "key_verification_bootstrap_not_setup_title") + } + /// %@ wants to verify + internal static func keyVerificationIncomingRequestIncomingAlertMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "key_verification_incoming_request_incoming_alert_message", p1) + } + /// Is the other device showing the same shield? + internal static var keyVerificationScanConfirmationScannedDeviceInformation: String { + return VectorL10n.tr("Vector", "key_verification_scan_confirmation_scanned_device_information") + } + /// Almost there! + internal static var keyVerificationScanConfirmationScannedTitle: String { + return VectorL10n.tr("Vector", "key_verification_scan_confirmation_scanned_title") + } + /// Is %@ showing the same shield? + internal static func keyVerificationScanConfirmationScannedUserInformation(_ p1: String) -> String { + return VectorL10n.tr("Vector", "key_verification_scan_confirmation_scanned_user_information", p1) + } + /// Waiting for other device… + internal static var keyVerificationScanConfirmationScanningDeviceWaitingOther: String { + return VectorL10n.tr("Vector", "key_verification_scan_confirmation_scanning_device_waiting_other") + } + /// Almost there! Waiting for confirmation… + internal static var keyVerificationScanConfirmationScanningTitle: String { + return VectorL10n.tr("Vector", "key_verification_scan_confirmation_scanning_title") + } + /// Waiting for %@… + internal static func keyVerificationScanConfirmationScanningUserWaitingOther(_ p1: String) -> String { + return VectorL10n.tr("Vector", "key_verification_scan_confirmation_scanning_user_waiting_other", p1) + } + /// Verified + internal static var keyVerificationTileConclusionDoneTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_conclusion_done_title") + } + /// Unstrusted sign in + internal static var keyVerificationTileConclusionWarningTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_conclusion_warning_title") + } + /// Accept + internal static var keyVerificationTileRequestIncomingApprovalAccept: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_approval_accept") + } + /// Decline + internal static var keyVerificationTileRequestIncomingApprovalDecline: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_approval_decline") + } + /// Verification request + internal static var keyVerificationTileRequestIncomingTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_title") + } + /// Verification sent + internal static var keyVerificationTileRequestOutgoingTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_outgoing_title") + } + /// You accepted + internal static var keyVerificationTileRequestStatusAccepted: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_accepted") + } + /// %@ cancelled + internal static func keyVerificationTileRequestStatusCancelled(_ p1: String) -> String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_cancelled", p1) + } + /// You cancelled + internal static var keyVerificationTileRequestStatusCancelledByMe: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_cancelled_by_me") + } + /// Data loading… + internal static var keyVerificationTileRequestStatusDataLoading: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_data_loading") + } + /// Expired + internal static var keyVerificationTileRequestStatusExpired: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_expired") + } + /// Waiting… + internal static var keyVerificationTileRequestStatusWaiting: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_waiting") + } + /// Verify user + internal static var keyVerificationUserTitle: String { + return VectorL10n.tr("Vector", "key_verification_user_title") + } + /// You’ve successfully verified this user. + internal static var keyVerificationVerifiedUserDescription1: String { + return VectorL10n.tr("Vector", "key_verification_verified_user_description_1") + } + /// Messages with this user in this room are end-to-end encrypted and can’t be read by third parties. + internal static var keyVerificationVerifiedUserDescription2: String { + return VectorL10n.tr("Vector", "key_verification_verified_user_description_2") + } + /// Can't scan? + internal static var keyVerificationVerifyQrCodeCannotScanAction: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_cannot_scan_action") + } + /// Scan the code to securely verify each other. + internal static var keyVerificationVerifyQrCodeInformation: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_information") + } + /// Did the other user successfully scan the QR code? + internal static var keyVerificationVerifyQrCodeOtherScanMyCodeTitle: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_other_scan_my_code_title") + } + /// Scan their code + internal static var keyVerificationVerifyQrCodeScanCodeAction: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_code_action") + } + /// QR code has been successfully validated. + internal static var keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_other_code_success_message") + } + /// Code validated! + internal static var keyVerificationVerifyQrCodeScanOtherCodeSuccessTitle: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_other_code_success_title") + } + /// Verify by scanning + internal static var keyVerificationVerifyQrCodeTitle: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_title") + } + /// Verify this user by confirming the following unique emoji appears on their screen, in the same order. + internal static var keyVerificationVerifyUserTitleEmoji: String { + return VectorL10n.tr("Vector", "key_verification_verify_user_title_emoji") + } + /// Verify this user by confirming the following numbers appear on their screen, in the same order. + internal static var keyVerificationVerifyUserTitleNumber: String { + return VectorL10n.tr("Vector", "key_verification_verify_user_title_number") + } /// %.1fK internal static func largeBadgeValueKFormat(_ p1: Float) -> String { return VectorL10n.tr("Vector", "large_badge_value_k_format", p1) @@ -1482,6 +1654,30 @@ internal enum VectorL10n { internal static var leave: String { return VectorL10n.tr("Vector", "leave") } + /// SESSION INFO + internal static var manageSessionInfo: String { + return VectorL10n.tr("Vector", "manage_session_info") + } + /// Session name + internal static var manageSessionName: String { + return VectorL10n.tr("Vector", "manage_session_name") + } + /// Not trusted + internal static var manageSessionNotTrusted: String { + return VectorL10n.tr("Vector", "manage_session_not_trusted") + } + /// Sign out of this session + internal static var manageSessionSignOut: String { + return VectorL10n.tr("Vector", "manage_session_sign_out") + } + /// Manage session + internal static var manageSessionTitle: String { + return VectorL10n.tr("Vector", "manage_session_title") + } + /// Trusted by you + internal static var manageSessionTrusted: String { + return VectorL10n.tr("Vector", "manage_session_trusted") + } /// Library internal static var mediaPickerLibrary: String { return VectorL10n.tr("Vector", "media_picker_library") @@ -1594,7 +1790,7 @@ internal enum VectorL10n { internal static var rename: String { return VectorL10n.tr("Vector", "rename") } - /// Please launch Riot on another device that can decrypt the message so it can send the keys to this device. + /// Please launch Riot on another device that can decrypt the message so it can send the keys to this session. internal static var rerequestKeysAlertMessage: String { return VectorL10n.tr("Vector", "rerequest_keys_alert_message") } @@ -1766,7 +1962,7 @@ internal enum VectorL10n { internal static var roomDetailsAddressesSection: String { return VectorL10n.tr("Vector", "room_details_addresses_section") } - /// Encrypt to verified devices only + /// Encrypt to verified sessions only internal static var roomDetailsAdvancedE2eEncryptionBlacklistUnverifiedDevices: String { return VectorL10n.tr("Vector", "room_details_advanced_e2e_encryption_blacklist_unverified_devices") } @@ -2090,6 +2286,30 @@ internal enum VectorL10n { internal static func roomManyUsersAreTyping(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "room_many_users_are_typing", p1, p2) } + /// Admin in %@ + internal static func roomMemberPowerLevelAdminIn(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_member_power_level_admin_in", p1) + } + /// Custom (%@) in %@ + internal static func roomMemberPowerLevelCustomIn(_ p1: String, _ p2: String) -> String { + return VectorL10n.tr("Vector", "room_member_power_level_custom_in", p1, p2) + } + /// Moderator in %@ + internal static func roomMemberPowerLevelModeratorIn(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_member_power_level_moderator_in", p1) + } + /// Admin + internal static var roomMemberPowerLevelShortAdmin: String { + return VectorL10n.tr("Vector", "room_member_power_level_short_admin") + } + /// Custom + internal static var roomMemberPowerLevelShortCustom: String { + return VectorL10n.tr("Vector", "room_member_power_level_short_custom") + } + /// Mod + internal static var roomMemberPowerLevelShortModerator: String { + return VectorL10n.tr("Vector", "room_member_power_level_short_moderator") + } /// Message edits internal static var roomMessageEditsHistoryTitle: String { return VectorL10n.tr("Vector", "room_message_edits_history_title") @@ -2170,7 +2390,7 @@ internal enum VectorL10n { internal static var roomParticipantsActionSectionAdminTools: String { return VectorL10n.tr("Vector", "room_participants_action_section_admin_tools") } - /// Devices + /// Sessions internal static var roomParticipantsActionSectionDevices: String { return VectorL10n.tr("Vector", "room_participants_action_section_devices") } @@ -2178,10 +2398,30 @@ internal enum VectorL10n { internal static var roomParticipantsActionSectionDirectChats: String { return VectorL10n.tr("Vector", "room_participants_action_section_direct_chats") } - /// Other + /// Options internal static var roomParticipantsActionSectionOther: String { return VectorL10n.tr("Vector", "room_participants_action_section_other") } + /// Security + internal static var roomParticipantsActionSectionSecurity: String { + return VectorL10n.tr("Vector", "room_participants_action_section_security") + } + /// Loading… + internal static var roomParticipantsActionSecurityStatusLoading: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_loading") + } + /// Verified + internal static var roomParticipantsActionSecurityStatusVerified: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_verified") + } + /// Verify + internal static var roomParticipantsActionSecurityStatusVerify: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_verify") + } + /// Warning + internal static var roomParticipantsActionSecurityStatusWarning: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_warning") + } /// Make admin internal static var roomParticipantsActionSetAdmin: String { return VectorL10n.tr("Vector", "room_participants_action_set_admin") @@ -2294,6 +2534,18 @@ internal enum VectorL10n { internal static var roomParticipantsRemoveThirdPartyInvitePromptMsg: String { return VectorL10n.tr("Vector", "room_participants_remove_third_party_invite_prompt_msg") } + /// Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them. + internal static var roomParticipantsSecurityInformationRoomEncrypted: String { + return VectorL10n.tr("Vector", "room_participants_security_information_room_encrypted") + } + /// Messages in this room are not end-to-end encrypted. + internal static var roomParticipantsSecurityInformationRoomNotEncrypted: String { + return VectorL10n.tr("Vector", "room_participants_security_information_room_not_encrypted") + } + /// Loading… + internal static var roomParticipantsSecurityLoading: String { + return VectorL10n.tr("Vector", "room_participants_security_loading") + } /// No identity server is configured so you cannot start a chat with a contact using an email. internal static var roomParticipantsStartNewChatErrorUsingUserEmailWithoutIdentityServer: String { return VectorL10n.tr("Vector", "room_participants_start_new_chat_error_using_user_email_without_identity_server") @@ -2470,7 +2722,7 @@ internal enum VectorL10n { internal static func roomUnsentMessagesNotification(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "room_unsent_messages_notification", p1, p2) } - /// Message not sent due to unknown devices being present. %@ or %@ now? + /// Message not sent due to unknown sessions being present. %@ or %@ now? internal static func roomUnsentMessagesUnknownDevicesNotification(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "room_unsent_messages_unknown_devices_notification", p1, p2) } @@ -2554,6 +2806,50 @@ internal enum VectorL10n { internal static var searchRooms: String { return VectorL10n.tr("Vector", "search_rooms") } + /// ADVANCED + internal static var securitySettingsAdvanced: String { + return VectorL10n.tr("Vector", "security_settings_advanced") + } + /// MESSAGE BACKUP + internal static var securitySettingsBackup: String { + return VectorL10n.tr("Vector", "security_settings_backup") + } + /// Never send messages to untrusted sessions + internal static var securitySettingsBlacklistUnverifiedDevices: String { + return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices") + } + /// Verify all of a users sessions to mark them as trusted and send messages to them. + internal static var securitySettingsBlacklistUnverifiedDevicesDescription: String { + return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices_description") + } + /// CROSS-SIGNING + internal static var securitySettingsCrosssigning: String { + return VectorL10n.tr("Vector", "security_settings_crosssigning") + } + /// MY SESSIONS + internal static var securitySettingsCryptoSessions: String { + return VectorL10n.tr("Vector", "security_settings_crypto_sessions") + } + /// Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup. + internal static var securitySettingsCryptoSessionsDescription: String { + return VectorL10n.tr("Vector", "security_settings_crypto_sessions_description") + } + /// Loading sessions… + internal static var securitySettingsCryptoSessionsLoading: String { + return VectorL10n.tr("Vector", "security_settings_crypto_sessions_loading") + } + /// CRYPTOGRAPHY + internal static var securitySettingsCryptography: String { + return VectorL10n.tr("Vector", "security_settings_cryptography") + } + /// Export keys manually + internal static var securitySettingsExportKeysManually: String { + return VectorL10n.tr("Vector", "security_settings_export_keys_manually") + } + /// Security + internal static var securitySettingsTitle: String { + return VectorL10n.tr("Vector", "security_settings_title") + } /// Send to %@ internal static func sendTo(_ p1: String) -> String { return VectorL10n.tr("Vector", "send_to", p1) @@ -2694,19 +2990,19 @@ internal enum VectorL10n { internal static var settingsCopyrightUrl: String { return VectorL10n.tr("Vector", "settings_copyright_url") } - /// Encrypt to verified devices only + /// Encrypt to verified sessions only internal static var settingsCryptoBlacklistUnverifiedDevices: String { return VectorL10n.tr("Vector", "settings_crypto_blacklist_unverified_devices") } - /// \nDevice ID: + /// \nSession ID: internal static var settingsCryptoDeviceId: String { return VectorL10n.tr("Vector", "settings_crypto_device_id") } - /// \nDevice key:\n + /// \nSession key:\n internal static var settingsCryptoDeviceKey: String { return VectorL10n.tr("Vector", "settings_crypto_device_key") } - /// Device Public Name: + /// Session name: internal static var settingsCryptoDeviceName: String { return VectorL10n.tr("Vector", "settings_crypto_device_name") } @@ -2726,11 +3022,11 @@ internal enum VectorL10n { internal static var settingsDeactivateMyAccount: String { return VectorL10n.tr("Vector", "settings_deactivate_my_account") } - /// DEVICES + /// SESSIONS internal static var settingsDevices: String { return VectorL10n.tr("Vector", "settings_devices") } - /// A device's public name is visible to people you communicate with + /// A session's public name is visible to people you communicate with internal static var settingsDevicesDescription: String { return VectorL10n.tr("Vector", "settings_devices_description") } @@ -2874,7 +3170,7 @@ internal enum VectorL10n { internal static var settingsKeyBackup: String { return VectorL10n.tr("Vector", "settings_key_backup") } - /// Connect this device to Key Backup + /// Connect this session to Key Backup internal static var settingsKeyBackupButtonConnect: String { return VectorL10n.tr("Vector", "settings_key_backup_button_connect") } @@ -2906,19 +3202,19 @@ internal enum VectorL10n { internal static func settingsKeyBackupInfoAlgorithm(_ p1: String) -> String { return VectorL10n.tr("Vector", "settings_key_backup_info_algorithm", p1) } - /// Checking... + /// Checking… internal static var settingsKeyBackupInfoChecking: String { return VectorL10n.tr("Vector", "settings_key_backup_info_checking") } - /// Your keys are not being backed up from this device. + /// Your keys are not being backed up from this session. internal static var settingsKeyBackupInfoNone: String { return VectorL10n.tr("Vector", "settings_key_backup_info_none") } - /// This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward. + /// This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward. internal static var settingsKeyBackupInfoNotValid: String { return VectorL10n.tr("Vector", "settings_key_backup_info_not_valid") } - /// Backing up %@ keys... + /// Backing up %@ keys… internal static func settingsKeyBackupInfoProgress(_ p1: String) -> String { return VectorL10n.tr("Vector", "settings_key_backup_info_progress", p1) } @@ -2926,7 +3222,7 @@ internal enum VectorL10n { internal static var settingsKeyBackupInfoProgressDone: String { return VectorL10n.tr("Vector", "settings_key_backup_info_progress_done") } - /// Connect this device to key backup before signing out to avoid losing any keys that may only be on this device. + /// Connect this session to key backup before signing out to avoid losing any keys that may only be on this device. internal static var settingsKeyBackupInfoSignoutWarning: String { return VectorL10n.tr("Vector", "settings_key_backup_info_signout_warning") } @@ -2938,11 +3234,11 @@ internal enum VectorL10n { internal static func settingsKeyBackupInfoTrustSignatureInvalidDeviceVerified(_ p1: String) -> String { return VectorL10n.tr("Vector", "settings_key_backup_info_trust_signature_invalid_device_verified", p1) } - /// Backup has a signature from device with ID: %@ + /// Backup has a signature from session with ID: %@ internal static func settingsKeyBackupInfoTrustSignatureUnknown(_ p1: String) -> String { return VectorL10n.tr("Vector", "settings_key_backup_info_trust_signature_unknown", p1) } - /// Backup has a valid signature from this device + /// Backup has a valid signature from this session internal static var settingsKeyBackupInfoTrustSignatureValid: String { return VectorL10n.tr("Vector", "settings_key_backup_info_trust_signature_valid") } @@ -2954,7 +3250,7 @@ internal enum VectorL10n { internal static func settingsKeyBackupInfoTrustSignatureValidDeviceVerified(_ p1: String) -> String { return VectorL10n.tr("Vector", "settings_key_backup_info_trust_signature_valid_device_verified", p1) } - /// This device is backing up your keys. + /// This session is backing up your keys. internal static var settingsKeyBackupInfoValid: String { return VectorL10n.tr("Vector", "settings_key_backup_info_valid") } @@ -2970,14 +3266,6 @@ internal enum VectorL10n { internal static var settingsLabsCreateConferenceWithJitsi: String { return VectorL10n.tr("Vector", "settings_labs_create_conference_with_jitsi") } - /// Cross-Signing - internal static var settingsLabsCrossSigning: String { - return VectorL10n.tr("Vector", "settings_labs_cross_signing") - } - /// Key verification by direct message - internal static var settingsLabsDmKeyVerification: String { - return VectorL10n.tr("Vector", "settings_labs_dm_key_verification") - } /// End-to-End Encryption internal static var settingsLabsE2eEncryption: String { return VectorL10n.tr("Vector", "settings_labs_e2e_encryption") @@ -3074,6 +3362,10 @@ internal enum VectorL10n { internal static var settingsReportBug: String { return VectorL10n.tr("Vector", "settings_report_bug") } + /// SECURITY + internal static var settingsSecurity: String { + return VectorL10n.tr("Vector", "settings_security") + } /// Send anon crash & usage data internal static var settingsSendCrashReport: String { return VectorL10n.tr("Vector", "settings_send_crash_report") @@ -3230,6 +3522,10 @@ internal enum VectorL10n { internal static var signOutNonExistingKeyBackupSignOutConfirmationAlertTitle: String { return VectorL10n.tr("Vector", "sign_out_non_existing_key_backup_sign_out_confirmation_alert_title") } + /// Skip + internal static var skip: String { + return VectorL10n.tr("Vector", "skip") + } /// Start internal static var start: String { return VectorL10n.tr("Vector", "start") @@ -3266,11 +3562,11 @@ internal enum VectorL10n { internal static var today: String { return VectorL10n.tr("Vector", "today") } - /// This room contains unknown devices which have not been verified.\nThis means there is no guarantee that the devices belong to the users they claim to.\nWe recommend you go through the verification process for each device before continuing, but you can resend the message without verifying if you prefer. + /// This room contains unknown sessions which have not been verified.\nThis means there is no guarantee that the sessions belong to the users they claim to.\nWe recommend you go through the verification process for each session before continuing, but you can resend the message without verifying if you prefer. internal static var unknownDevicesAlert: String { return VectorL10n.tr("Vector", "unknown_devices_alert") } - /// Room contains unknown devices + /// Room contains unknown sessions internal static var unknownDevicesAlertTitle: String { return VectorL10n.tr("Vector", "unknown_devices_alert_title") } @@ -3286,7 +3582,7 @@ internal enum VectorL10n { internal static var unknownDevicesSendAnyway: String { return VectorL10n.tr("Vector", "unknown_devices_send_anyway") } - /// Unknown devices + /// Unknown sessions internal static var unknownDevicesTitle: String { return VectorL10n.tr("Vector", "unknown_devices_title") } @@ -3294,6 +3590,98 @@ internal enum VectorL10n { internal static var unknownDevicesVerify: String { return VectorL10n.tr("Vector", "unknown_devices_verify") } + /// If you didn’t sign in to this session, your account may be compromised. + internal static var userVerificationSessionDetailsAdditionalInformationUntrustedCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_additional_information_untrusted_current_user") + } + /// Until this user trusts this session, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it. + internal static var userVerificationSessionDetailsAdditionalInformationUntrustedOtherUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_additional_information_untrusted_other_user") + } + /// This session is trusted for secure messaging because you verified it: + internal static var userVerificationSessionDetailsInformationTrustedCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_trusted_current_user") + } + /// This session is trusted for secure messaging because + internal static var userVerificationSessionDetailsInformationTrustedOtherUserPart1: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_trusted_other_user_part1") + } + /// verified it: + internal static var userVerificationSessionDetailsInformationTrustedOtherUserPart2: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_trusted_other_user_part2") + } + /// Verify this session to mark it as trusted & grant it access to encrypted messages: + internal static var userVerificationSessionDetailsInformationUntrustedCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_untrusted_current_user") + } + /// signed in using a new session: + internal static var userVerificationSessionDetailsInformationUntrustedOtherUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_untrusted_other_user") + } + /// Trusted + internal static var userVerificationSessionDetailsTrustedTitle: String { + return VectorL10n.tr("Vector", "user_verification_session_details_trusted_title") + } + /// Warning + internal static var userVerificationSessionDetailsUntrustedTitle: String { + return VectorL10n.tr("Vector", "user_verification_session_details_untrusted_title") + } + /// Verify + internal static var userVerificationSessionDetailsVerifyActionCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_verify_action_current_user") + } + /// Manually verify + internal static var userVerificationSessionDetailsVerifyActionOtherUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_verify_action_other_user") + } + /// Messages with this user in this room are end-to-end encrypted and can’t be read by third parties. + internal static var userVerificationSessionsListInformation: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_information") + } + /// Trusted + internal static var userVerificationSessionsListSessionTrusted: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_session_trusted") + } + /// Not trusted + internal static var userVerificationSessionsListSessionUntrusted: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_session_untrusted") + } + /// Sessions + internal static var userVerificationSessionsListTableTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_table_title") + } + /// Trusted + internal static var userVerificationSessionsListUserTrustLevelTrustedTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_user_trust_level_trusted_title") + } + /// Unknown + internal static var userVerificationSessionsListUserTrustLevelUnknownTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_user_trust_level_unknown_title") + } + /// Warning + internal static var userVerificationSessionsListUserTrustLevelWarningTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_user_trust_level_warning_title") + } + /// To be secure, do this in person or use another way to communicate. + internal static var userVerificationStartAdditionalInformation: String { + return VectorL10n.tr("Vector", "user_verification_start_additional_information") + } + /// For extra security, verify + internal static var userVerificationStartInformationPart1: String { + return VectorL10n.tr("Vector", "user_verification_start_information_part1") + } + /// by checking a one-time code on both your devices. + internal static var userVerificationStartInformationPart2: String { + return VectorL10n.tr("Vector", "user_verification_start_information_part2") + } + /// Start verification + internal static var userVerificationStartVerifyAction: String { + return VectorL10n.tr("Vector", "user_verification_start_verify_action") + } + /// Waiting for %@… + internal static func userVerificationStartWaitingPartner(_ p1: String) -> String { + return VectorL10n.tr("Vector", "user_verification_start_waiting_partner", p1) + } /// Video internal static var video: String { return VectorL10n.tr("Vector", "video") @@ -3386,7 +3774,7 @@ internal enum VectorL10n { internal static var widgetNoPowerToManage: String { return VectorL10n.tr("Vector", "widget_no_power_to_manage") } - /// Manage integrations... + /// Manage integrations… internal static var widgetPickerManageIntegrations: String { return VectorL10n.tr("Vector", "widget_picker_manage_integrations") } diff --git a/Riot/Managers/OnBoarding/OnBoardingManager.swift b/Riot/Managers/OnBoarding/OnBoardingManager.swift index 9656fb213..f224383d4 100644 --- a/Riot/Managers/OnBoarding/OnBoardingManager.swift +++ b/Riot/Managers/OnBoarding/OnBoardingManager.swift @@ -56,7 +56,8 @@ final public class OnBoardingManager: NSObject { case .success: // Create DM room with Riot-bot - let httpOperation = self.session.createRoom(name: nil, visibility: .private, alias: nil, topic: nil, invite: [Constants.riotBotMatrixId], invite3PID: nil, isDirect: true, preset: .trustedPrivateChat) { (response) in + let roomCreationParameters = MXRoomCreationParameters(forDirectRoomWithUser: Constants.riotBotMatrixId) + let httpOperation = self.session.createRoom(parameters: roomCreationParameters) { (response) in switch response { case .success: diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index b98096849..271c053bf 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -32,17 +32,9 @@ final class RiotSettings: NSObject { static let pinRoomsWithUnreadMessages = "pinRoomsWithUnread" static let allowStunServerFallback = "allowStunServerFallback" static let stunServerFallback = "stunServerFallback" - static let enableCrossSigning = "enableCrossSigning" static let enableDMKeyVerification = "enableDMKeyVerification" } - - /// Riot Standard Room Member Power Level - @objc - public enum RoomPowerLevel: Int { - case moderator = 50 - case admin = 100 - } - + static let shared = RiotSettings() // MARK: - Public @@ -123,22 +115,6 @@ final class RiotSettings: NSObject { UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) } } - - var enableDMKeyVerification: Bool { - get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableDMKeyVerification) - } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableDMKeyVerification) - } - } - - var enableCrossSigning: Bool { - get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableCrossSigning) - } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableCrossSigning) - } - } // MARK: Calls diff --git a/Riot/Model/Room/RoomPowerLevel.swift b/Riot/Model/Room/RoomPowerLevel.swift new file mode 100644 index 000000000..38d6d1d90 --- /dev/null +++ b/Riot/Model/Room/RoomPowerLevel.swift @@ -0,0 +1,44 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// Riot Standard Room Member Power Level +@objc +public enum RoomPowerLevel: Int { + case admin = 100 + case moderator = 50 + case user = 0 + + public init?(rawValue: Int) { + switch rawValue { + case 100...: + self = .admin + case 50...99: + self = .moderator + default: + self = .user + } + } +} + +@objcMembers +public final class RoomPowerLevelHelper: NSObject { + + static func roomPowerLevel(from rawValue: Int) -> RoomPowerLevel { + return RoomPowerLevel(rawValue: rawValue) ?? .user + } +} diff --git a/Riot/Model/Room/RoomPreviewData.h b/Riot/Model/Room/RoomPreviewData.h index 47797327c..71280b099 100644 --- a/Riot/Model/Room/RoomPreviewData.h +++ b/Riot/Model/Room/RoomPreviewData.h @@ -62,6 +62,7 @@ @property (nonatomic) NSString *roomName; @property (nonatomic, readonly) NSString *roomTopic; @property (nonatomic, readonly) NSString *roomAvatarUrl; +@property (nonatomic, readonly) NSString *roomCanonicalAlias; @property (nonatomic, readonly) NSArray *roomAliases; @property (nonatomic, readonly) NSInteger numJoinedMembers; // -1 if unknown. diff --git a/Riot/Model/Room/RoomPreviewData.m b/Riot/Model/Room/RoomPreviewData.m index b86f1b7ef..74738367a 100644 --- a/Riot/Model/Room/RoomPreviewData.m +++ b/Riot/Model/Room/RoomPreviewData.m @@ -54,14 +54,22 @@ _roomName = publicRoom.displayname; _roomAvatarUrl = publicRoom.avatarUrl; _roomTopic = publicRoom.topic; + _roomCanonicalAlias = publicRoom.canonicalAlias; _roomAliases = publicRoom.aliases; _numJoinedMembers = publicRoom.numJoinedMembers; // First try to fallback to the name if displayname isn't present - if (!_roomName.length) { + if (!_roomName.length) + { _roomName = publicRoom.name; } + if (!_roomName.length) + { + // Use the canonical alias if present. + _roomName = publicRoom.canonicalAlias; + } + if (!_roomName.length) { // Consider the room aliases to define a default room name. diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 2ba421545..1155c89a5 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -25,7 +25,7 @@ #import "ForgotPasswordInputsView.h" #import "AuthFallBackViewController.h" -@interface AuthenticationViewController () +@interface AuthenticationViewController () { /** Store the potential login error received by using a default homeserver different from matrix.org @@ -52,6 +52,7 @@ } @property (nonatomic, readonly) BOOL isIdentityServerConfigured; +@property (nonatomic, strong) KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; @end @@ -238,6 +239,11 @@ - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + + if (self.keyVerificationCoordinatorBridgePresenter) + { + return; + } // Verify that the app does not show the authentification screean whereas // the user has already logged in. @@ -264,6 +270,7 @@ } autoDiscovery = nil; + _keyVerificationCoordinatorBridgePresenter = nil; } - (BOOL)isIdentityServerConfigured @@ -423,6 +430,40 @@ } } +- (void)presentCompleteSecurityWithSession:(MXSession*)session +{ + KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:session]; + keyVerificationCoordinatorBridgePresenter.delegate = self; + + if (self.navigationController) + { + [keyVerificationCoordinatorBridgePresenter pushCompleteSecurityFrom:self.navigationController animated:YES]; + } + else + { + [keyVerificationCoordinatorBridgePresenter presentCompleteSecurityFrom:self animated:YES]; + } + + self.keyVerificationCoordinatorBridgePresenter = keyVerificationCoordinatorBridgePresenter; +} + +- (void)dismiss +{ + self.userInteractionEnabled = YES; + [self.authenticationActivityIndicator stopAnimating]; + + // Remove auth view controller on successful login + if (self.navigationController) + { + // Pop the view controller + [self.navigationController popViewControllerAnimated:YES]; + } + else + { + // Dismiss on successful login + [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; + } +} #pragma mark - Fallback URL display @@ -1102,40 +1143,82 @@ - (void)authenticationViewController:(MXKAuthenticationViewController *)authenticationViewController didLogWithUserId:(NSString *)userId { + self.userInteractionEnabled = NO; + [self.authenticationActivityIndicator startAnimating]; + // Hide the custom server details in order to save customized inputs [self hideCustomServers:YES]; + MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:userId]; + MXSession *session = account.mxSession; + // Create DM with Riot-bot on new account creation. if (self.authType == MXKAuthenticationTypeRegister) { - MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:userId]; - - [account.mxSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:@[@"@riot-bot:matrix.org"] - invite3PID:nil - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - success:nil - failure:^(NSError *error) { - - NSLog(@"[AuthenticationVC] Create chat with riot-bot failed"); - - }]; + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:@"@riot-bot:matrix.org"]; + [session createRoomWithParameters:roomCreationParameters success:nil failure:^(NSError *error) { + NSLog(@"[AuthenticationVC] Create chat with riot-bot failed"); + }]; } - // Remove auth view controller on successful login - if (self.navigationController) + // Wait for session change to present complete security screen if needed + [self registerSessionStateChangeNotificationForSession:session]; +} + +- (void)registerSessionStateChangeNotificationForSession:(MXSession*)session +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionStateDidChangeNotification:) name:kMXSessionStateDidChangeNotification object:session]; +} + +- (void)unregisterSessionStateChangeNotification +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionStateDidChangeNotification object:nil]; +} + +- (void)sessionStateDidChangeNotification:(NSNotification*)notification +{ + MXSession *session = (MXSession*)notification.object; + + if (session.state >= MXSessionStateStoreDataReady) { - // Pop the view controller - [self.navigationController popViewControllerAnimated:YES]; - } - else - { - // Dismiss on successful login - [self dismissViewControllerAnimated:YES completion:nil]; + [self unregisterSessionStateChangeNotification]; + + if (session.crypto.crossSigning) + { + // Do not make key share requests while the "Complete security" is not complete. + // If the device is self-verified, the SDK will restore the existing key backup. + // Then, it will re-enable outgoing key share requests + [session.crypto setOutgoingKeyRequestsEnabled:NO onComplete:nil]; + + [session.crypto.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { + + if (session.crypto.crossSigning.state == MXCrossSigningStateCrossSigningExists) + { + dispatch_async(dispatch_get_main_queue(), ^{ + + self.userInteractionEnabled = YES; + [self.authenticationActivityIndicator stopAnimating]; + + [self presentCompleteSecurityWithSession:session]; + }); + } + else + { + [session.crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; + [self dismiss]; + } + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[AuthenticationVC] Fail to refresh crypto state with error: %@", error); + dispatch_async(dispatch_get_main_queue(), ^{ + [self dismiss]; + }); + }]; + } + else + { + [self dismiss]; + } } } @@ -1268,4 +1351,19 @@ [self hideCustomServers:NO]; } +#pragma mark - KeyVerificationCoordinatorBridgePresenterDelegate + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismiss]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + // Set outgoing key requests back + [coordinatorBridgePresenter.session.crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; + + [self dismiss]; +} + @end diff --git a/Riot/Modules/Camera/CameraAccessAlertPresenter.swift b/Riot/Modules/Camera/CameraAccessAlertPresenter.swift new file mode 100644 index 000000000..721e9f596 --- /dev/null +++ b/Riot/Modules/Camera/CameraAccessAlertPresenter.swift @@ -0,0 +1,61 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class CameraAccessAlertPresenter { + + // MARK: - Public + + func presentPermissionDeniedAlert(from presentingViewController: UIViewController, animated: Bool) { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { + return + } + + let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" + + let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraAccessNotGranted(appDisplayName), preferredStyle: .alert) + + let cancelActionTitle = Bundle.mxk_localizedString(forKey: "ok") + let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: { _ in + }) + + let settingsActionTitle = Bundle.mxk_localizedString(forKey: "settings") + let settingsAction = UIAlertAction(title: settingsActionTitle, style: .default, handler: { _ in + UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (succeed) in + if !succeed { + print("[CameraPresenter] Fails to open settings") + } + }) + }) + + alert.addAction(cancelAction) + alert.addAction(settingsAction) + + presentingViewController.present(alert, animated: animated, completion: nil) + } + + func presentCameraUnavailableAlert(from presentingViewController: UIViewController, animated: Bool) { + + let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraUnavailable, preferredStyle: .alert) + + let okAction = UIAlertAction(title: VectorL10n.accept, style: .default, handler: nil) + + alert.addAction(okAction) + + presentingViewController.present(alert, animated: true, completion: nil) + } +} diff --git a/Riot/Modules/Camera/CameraAccessManager.swift b/Riot/Modules/Camera/CameraAccessManager.swift new file mode 100644 index 000000000..93cacaafc --- /dev/null +++ b/Riot/Modules/Camera/CameraAccessManager.swift @@ -0,0 +1,61 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// CameraAccessManager handles camera availability and authorization. +final class CameraAccessManager { + + // MARK: - Properties + + var isCameraAvailable: Bool { + return UIImagePickerController.isSourceTypeAvailable(.camera) + } + + var isCameraAccessGranted: Bool { + return AVCaptureDevice.authorizationStatus(for: .video) == .authorized + } + + // MARK: - Public + + func askAndRequestCameraAccessIfNeeded(completion: @escaping (_ granted: Bool) -> Void) { + + let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + + switch authorizationStatus { + case .authorized: + completion(true) + case .notDetermined: + self.requestCameraAccess(completion: { (granted) in + completion(granted) + }) + case .denied, .restricted: + completion(false) + @unknown default: + break + } + } + + // MARK: - Private + + private func requestCameraAccess(completion: @escaping (_ granted: Bool) -> Void) { + AVCaptureDevice.requestAccess(for: .video) { granted in + DispatchQueue.main.async { + completion(granted) + } + } + } +} diff --git a/Riot/Modules/Camera/CameraPresenter.swift b/Riot/Modules/Camera/CameraPresenter.swift index 6ab0a6eee..749e9abe5 100644 --- a/Riot/Modules/Camera/CameraPresenter.swift +++ b/Riot/Modules/Camera/CameraPresenter.swift @@ -35,16 +35,27 @@ import AVFoundation // MARK: - Properties - // MARK: - Private + // MARK: Private + + private let cameraAccessManager: CameraAccessManager + private let cameraAccessAlertPresenter: CameraAccessAlertPresenter private weak var presentingViewController: UIViewController? private weak var cameraViewController: UIViewController? private var mediaUTIs: [MXKUTI] = [] - // MARK: - Public + // MARK: Public @objc weak var delegate: CameraPresenterDelegate? + // MARK: - Setup + + override init() { + self.cameraAccessManager = CameraAccessManager() + self.cameraAccessAlertPresenter = CameraAccessAlertPresenter() + super.init() + } + // MARK: - Public @objc func presentCamera(from presentingViewController: UIViewController, with mediaUTIs: [MXKUTI], animated: Bool) { @@ -63,24 +74,21 @@ import AVFoundation // MARK: - Private private func checkCameraPermissionAndPresentCamera(animated: Bool) { + guard let presentingViewController = self.presentingViewController else { + return + } - let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + guard self.cameraAccessManager.isCameraAvailable else { + self.cameraAccessAlertPresenter.presentCameraUnavailableAlert(from: presentingViewController, animated: animated) + return + } - switch authorizationStatus { - case .authorized: - self.presentCameraController(animated: animated) - case .notDetermined: - self.requestCameraAccess(completion: { (granted) in - if granted { - self.presentCameraController(animated: animated) - } else { - self.presentPermissionDeniedAlert() - } - }) - case .denied, .restricted: - self.presentPermissionDeniedAlert() - @unknown default: - break + self.cameraAccessManager.askAndRequestCameraAccessIfNeeded { (granted) in + if granted { + self.presentCameraController(animated: animated) + } else { + self.cameraAccessAlertPresenter.presentPermissionDeniedAlert(from: presentingViewController, animated: animated) + } } } @@ -114,56 +122,6 @@ import AVFoundation return imagePickerController } - - private func requestCameraAccess(completion: @escaping (_ granted: Bool) -> Void) { - AVCaptureDevice.requestAccess(for: .video) { granted in - DispatchQueue.main.async { - completion(granted) - } - } - } - - private func presentPermissionDeniedAlert() { - guard let presentingViewController = self.presentingViewController, let settingsURL = URL(string: UIApplication.openSettingsURLString) else { - return - } - - let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" - - let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraAccessNotGranted(appDisplayName), preferredStyle: .alert) - - let cancelActionTitle = Bundle.mxk_localizedString(forKey: "ok") - let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: { _ in - }) - - let settingsActionTitle = Bundle.mxk_localizedString(forKey: "settings") - let settingsAction = UIAlertAction(title: settingsActionTitle, style: .default, handler: { _ in - UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (succeed) in - if !succeed { - print("[CameraPresenter] Fails to open settings") - } - }) - }) - - alert.addAction(cancelAction) - alert.addAction(settingsAction) - - presentingViewController.present(alert, animated: true, completion: nil) - } - - private func presentCameraUnavailableAlert() { - guard let presentingViewController = self.presentingViewController else { - return - } - - let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraUnavailable, preferredStyle: .alert) - - let okAction = UIAlertAction(title: VectorL10n.accept, style: .default, handler: nil) - - alert.addAction(okAction) - - presentingViewController.present(alert, animated: true, completion: nil) - } } // MARK: - UIImagePickerControllerDelegate diff --git a/Riot/Modules/Common/Buttons/Close/CloseButton.swift b/Riot/Modules/Common/Buttons/Close/CloseButton.swift new file mode 100644 index 000000000..a46a0a9f2 --- /dev/null +++ b/Riot/Modules/Common/Buttons/Close/CloseButton.swift @@ -0,0 +1,95 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class CloseButton: UIButton, Themable { + + // MARK: - Constants + + private enum CircleBackgroundConstants { + static let height: CGFloat = 30.0 + static let highlightedAlha: CGFloat = 0.5 + static let normalAlha: CGFloat = 1.0 + } + + // MARK: - Properties + + // MARK: Private + + private var theme: Theme? + + private var circleBackgroundView: UIView! + + // MARK: Public + + override var isHighlighted: Bool { + didSet { + self.circleBackgroundView.alpha = self.isHighlighted ? CircleBackgroundConstants.highlightedAlha : CircleBackgroundConstants.normalAlha + } + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.backgroundColor = UIColor.clear + self.setImage(Asset.Images.closeButton.image, for: .normal) + self.setupCircleView() + + self.update(theme: ThemeService.shared().theme) + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.sendSubviewToBack(self.circleBackgroundView) + self.circleBackgroundView.layer.cornerRadius = self.circleBackgroundView.bounds.height/2 + } + + // MARK: - Private + + private func setupCircleView() { + + let rect = CGRect(x: 0, y: 0, width: CircleBackgroundConstants.height, height: CircleBackgroundConstants.height) + let view = UIView(frame: rect) + view.translatesAutoresizingMaskIntoConstraints = false + view.isUserInteractionEnabled = false + view.layer.masksToBounds = true + + self.addSubview(view) + + NSLayoutConstraint.activate([ + view.heightAnchor.constraint(equalToConstant: CircleBackgroundConstants.height), + view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1.0), + view.centerXAnchor.constraint(equalTo: self.centerXAnchor), + view.centerYAnchor.constraint(equalTo: self.centerYAnchor) + ]) + + self.sendSubviewToBack(view) + + self.circleBackgroundView = view + } + + // MARK: - Themable + + func update(theme: Theme) { + self.theme = theme + + self.circleBackgroundView.backgroundColor = theme.headerTextSecondaryColor + } +} diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 8f17b3f77..d8060b9c8 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -240,6 +240,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou break; } + [self updateKeyBackupBanner]; [self forceRefresh]; } diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m index 1f7e1b443..a33e0e5e0 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -137,7 +137,15 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; self.directRoomBorderView.hidden = !roomCellData.roomSummary.room.isDirect; - self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; + if (roomCellData.roomSummary.isEncrypted) + { + self.encryptedRoomIcon.hidden = NO; + self.encryptedRoomIcon.image = [self shieldImageForTrustLevel:roomCellData.roomSummary.roomEncryptionTrustLevel]; + } + else + { + self.encryptedRoomIcon.hidden = YES; + } [roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar]; } @@ -153,4 +161,32 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; return 74; } +- (UIImage*)shieldImageForTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + UIImage *shieldImage; + + NSString *encryptionIconName; + switch (roomEncryptionTrustLevel) + { + case RoomEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case RoomEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case RoomEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + case RoomEncryptionTrustLevelUnknown: + encryptionIconName = @"encryption_normal"; + break; + } + + if (encryptionIconName) + { + shieldImage = [UIImage imageNamed:encryptionIconName]; + } + return shieldImage; +} + @end diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib index 0303e1aca..8fd41d271 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -75,10 +75,10 @@ @@ -112,11 +112,11 @@ - + - + diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m index fb9e86b8b..b4c48231b 100644 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m +++ b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m @@ -826,12 +826,15 @@ { [participantCell render:contact]; - // Update member badge + NSString *powerLevelText; + + // Update power level label if (contact.mxGroupUser.isPrivileged) { - participantCell.thumbnailBadgeView.image = [UIImage imageNamed:@"admin_icon"]; - participantCell.thumbnailBadgeView.hidden = NO; + powerLevelText = NSLocalizedStringFromTable(@"room_member_power_level_short_admin", @"Vector", nil); } + + participantCell.powerLevelLabel.text = powerLevelText; } cell = participantCell; diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m index f8fc01553..a6c968496 100644 --- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m +++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m @@ -20,6 +20,7 @@ #import "AppDelegate.h" #import "Riot-Swift.h" +#import "MXSession+Riot.h" #import "RoomMemberTitleView.h" @@ -1040,37 +1041,52 @@ { inviteArray = @[participantId]; } - + + MXWeakify(self); + void (^onFailure)(NSError *) = ^(NSError *error){ + MXStrongifyAndReturnIfNil(self); + + NSLog(@"[ContactDetailsViewController] Create room failed"); + + self->roomCreationRequest = nil; + + [self removePendingActionMask]; + + // Notify user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }; + + // Create a new room - roomCreationRequest = [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:inviteArray - invite3PID:invite3PIDArray - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - success:^(MXRoom *room) { - - roomCreationRequest = nil; - - [self removePendingActionMask]; - - [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; - - } - failure:^(NSError *error) { - - NSLog(@"[ContactDetailsViewController] Create room failed"); - - roomCreationRequest = nil; - - [self removePendingActionMask]; - - // Notify user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; + [self.mainSession canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) { + MXStrongifyAndReturnIfNil(self); + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = inviteArray; + roomCreationParameters.invite3PIDArray = invite3PIDArray; + roomCreationParameters.isDirect = YES; + roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; + + if (canEnableE2E && roomCreationParameters.invite3PIDArray == nil) + { + roomCreationParameters.initialStateEvents = @[ + [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm + ]]; + } + + + self->roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + self->roomCreationRequest = nil; + + [self removePendingActionMask]; + + [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; + + } failure:onFailure]; + + } failure:onFailure]; } break; } @@ -1093,36 +1109,28 @@ else { // Create a new room - roomCreationRequest = [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:@[matrixId] - invite3PID:nil - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - success:^(MXRoom *room) { - - roomCreationRequest = nil; - - // Delay the call in order to be sure that the room is ready - dispatch_async(dispatch_get_main_queue(), ^{ - [room placeCallWithVideo:isVideoCall success:nil failure:nil]; - [self removePendingActionMask]; - }); - - } failure:^(NSError *error) { - - NSLog(@"[ContactDetailsViewController] Create room failed"); - - roomCreationRequest = nil; - - [self removePendingActionMask]; - - // Notify user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:matrixId]; + roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + roomCreationRequest = nil; + + // Delay the call in order to be sure that the room is ready + dispatch_async(dispatch_get_main_queue(), ^{ + [room placeCallWithVideo:isVideoCall success:nil failure:nil]; + [self removePendingActionMask]; + }); + + } failure:^(NSError *error) { + + NSLog(@"[ContactDetailsViewController] Create room failed"); + + roomCreationRequest = nil; + + [self removePendingActionMask]; + + // Notify user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; } break; } diff --git a/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib b/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib index 2666caf2f..633ae0a98 100644 --- a/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib +++ b/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib @@ -1,13 +1,11 @@ - + - - - + @@ -57,14 +55,15 @@ + - + diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.h b/Riot/Modules/Contacts/Views/ContactTableViewCell.h index 32a7ff69c..eadf78150 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.h +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.h @@ -28,11 +28,12 @@ MXKContact *contact; } -@property (nonatomic) IBOutlet MXKImageView *thumbnailView; -@property (nonatomic) IBOutlet UIImageView *thumbnailBadgeView; -@property (nonatomic) IBOutlet UILabel *contactDisplayNameLabel; -@property (nonatomic) IBOutlet UILabel *contactInformationLabel; -@property (nonatomic) IBOutlet UIView *customAccessoryView; +@property (weak, nonatomic) IBOutlet MXKImageView *thumbnailView; +@property (weak, nonatomic) IBOutlet UILabel *contactDisplayNameLabel; +@property (weak, nonatomic) IBOutlet UILabel *powerLevelLabel; +@property (weak, nonatomic) IBOutlet UILabel *contactInformationLabel; +@property (weak, nonatomic) IBOutlet UIView *customAccessoryView; +@property (weak, nonatomic) IBOutlet UIImageView *avatarBadgeImageView; @property (nonatomic) BOOL showCustomAccessoryView; diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.m b/Riot/Modules/Contacts/Views/ContactTableViewCell.m index f55ce88e9..500d75a79 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.m +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.m @@ -24,6 +24,7 @@ #import "AvatarGenerator.h" #import "Tools.h" +#import "MXRoom+Riot.h" #import "NBPhoneNumberUtil.h" @@ -55,6 +56,7 @@ // apply the vector colours self.contactDisplayNameLabel.textColor = ThemeService.shared.theme.textPrimaryColor; self.contactInformationLabel.textColor = ThemeService.shared.theme.textSecondaryColor; + self.powerLevelLabel.textColor = ThemeService.shared.theme.textSecondaryColor; // Clear the default background color of a MXKImageView instance self.thumbnailView.defaultBackgroundColor = [UIColor clearColor]; @@ -133,8 +135,6 @@ mxPresenceObserver = nil; } - self.thumbnailBadgeView.hidden = YES; - // Sanity check: accept only object of MXKContact classes or sub-classes NSParameterAssert([cellData isKindOfClass:[MXKContact class]]); contact = (MXKContact*)cellData; @@ -146,6 +146,7 @@ self.thumbnailView.image = nil; self.contactDisplayNameLabel.text = nil; self.contactInformationLabel.text = nil; + self.powerLevelLabel.text = nil; return; } @@ -171,6 +172,7 @@ }]; [self refreshContactPresence]; + [self refreshContactBadgeImage]; } else { @@ -234,6 +236,20 @@ self.thumbnailView.image = image; } +- (void)refreshContactBadgeImage +{ + NSString *matrixId = [self firstMatrixId]; + + UserEncryptionTrustLevel userEncryptionTrustLevel = UserEncryptionTrustLevelUnknown; + + if (matrixId) + { + userEncryptionTrustLevel = [self.mxRoom encryptionTrustLevelForUserId:matrixId]; + } + + self.avatarBadgeImageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:userEncryptionTrustLevel]; +} + - (void)refreshContactDisplayName { self.contactDisplayNameLabel.text = contact.displayName; @@ -267,18 +283,7 @@ } - (void)refreshLocalContactInformation -{ - NSArray *identifiers = contact.matrixIdentifiers; - if (identifiers.count) - { - self.thumbnailBadgeView.image = [UIImage imageNamed:@"riot_icon"]; - self.thumbnailBadgeView.hidden = NO; - } - else - { - self.thumbnailBadgeView.hidden = YES; - } - +{ // Display the first contact method in sub label. NSString *subLabelText = nil; if (contact.emailAddresses.count) diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.xib b/Riot/Modules/Contacts/Views/ContactTableViewCell.xib index 2209926d9..233951636 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.xib +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -27,23 +27,28 @@ - diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift deleted file mode 100644 index 3687da88a..000000000 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift +++ /dev/null @@ -1,192 +0,0 @@ -// File created from FlowTemplate -// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import UIKit - -@objcMembers -final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { - - // MARK: - Properties - - // MARK: Private - - private let navigationRouter: NavigationRouterType - private let session: MXSession - private let otherUserId: String - private let otherDeviceId: String - - private var incomingTransaction: MXIncomingSASTransaction? - - // MARK: Public - - // Must be used only internally - var childCoordinators: [Coordinator] = [] - - weak var delegate: DeviceVerificationCoordinatorDelegate? - - // MARK: - Setup - - /// Contrustor to start a verification of another device. - /// - /// - Parameters: - /// - session: the MXSession - /// - otherUserId: the device user id - /// - otherDevice: the device id - init(session: MXSession, otherUserId: String, otherDeviceId: String) { - self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) - self.session = session - self.otherUserId = otherUserId - self.otherDeviceId = otherDeviceId - } - - /// Contrustor to manage an incoming SAS device verification transaction - /// - /// - Parameters: - /// - session: the MXSession - /// - transaction: an existing device verification transaction - convenience init(session: MXSession, incomingTransaction: MXIncomingSASTransaction) { - self.init(session: session, - otherUserId: incomingTransaction.otherUserId, - otherDeviceId: incomingTransaction.otherDeviceId) - self.incomingTransaction = incomingTransaction - } - - // MARK: - Public methods - - func start() { - let rootCoordinator = self.createDataLoadingScreenCoordinator() - rootCoordinator.start() - - self.add(childCoordinator: rootCoordinator) - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in - self?.remove(childCoordinator: rootCoordinator) - } - } - - func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() - } - - // MARK: - Private methods - - private func createDataLoadingScreenCoordinator() -> DeviceVerificationDataLoadingCoordinator { - let coordinator = DeviceVerificationDataLoadingCoordinator(session: self.session, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - coordinator.delegate = self - coordinator.start() - - return coordinator - } - - private func showStart(otherUser: MXUser, otherDevice: MXDeviceInfo) { - let coordinator = DeviceVerificationStartCoordinator(session: self.session, otherUser: otherUser, otherDevice: otherDevice) - coordinator.delegate = self - coordinator.start() - - self.add(childCoordinator: coordinator) - self.navigationRouter.setRootModule(coordinator) { [weak self] in - self?.remove(childCoordinator: coordinator) - } - } - - private func showIncoming(otherUser: MXUser, transaction: MXIncomingSASTransaction) { - let coordinator = DeviceVerificationIncomingCoordinator(session: self.session, otherUser: otherUser, transaction: transaction) - coordinator.delegate = self - coordinator.start() - - self.add(childCoordinator: coordinator) - self.navigationRouter.setRootModule(coordinator) { [weak self] in - self?.remove(childCoordinator: coordinator) - } - } - - private func showVerify(transaction: MXSASTransaction, animated: Bool) { - let coordinator = DeviceVerificationVerifyCoordinator(session: self.session, transaction: transaction) - coordinator.delegate = self - coordinator.start() - - self.add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: animated) { [weak self] in - self?.remove(childCoordinator: coordinator) - } - } - - private func showVerified(animated: Bool) { - let viewController = DeviceVerificationVerifiedViewController.instantiate() - viewController.delegate = self - self.navigationRouter.setRootModule(viewController) - } -} - -extension DeviceVerificationCoordinator: DeviceVerificationDataLoadingCoordinatorDelegate { - func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) { - - if let incomingTransaction = self.incomingTransaction { - self.showIncoming(otherUser: user, transaction: incomingTransaction) - } else { - self.showStart(otherUser: user, otherDevice: device) - } - } - - func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } -} - -extension DeviceVerificationCoordinator: DeviceVerificationStartCoordinatorDelegate { - func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { - self.showVerify(transaction: transaction, animated: true) - } - - func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } - - func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } -} - -extension DeviceVerificationCoordinator: DeviceVerificationIncomingCoordinatorDelegate { - func deviceVerificationIncomingCoordinator(_ coordinator: DeviceVerificationIncomingCoordinatorType, didAcceptTransaction transaction: MXSASTransaction) { - self.showVerify(transaction: transaction, animated: true) - } - - func deviceVerificationIncomingCoordinatorDidCancel(_ coordinator: DeviceVerificationIncomingCoordinatorType) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } -} - -extension DeviceVerificationCoordinator: DeviceVerificationVerifyCoordinatorDelegate { - func deviceVerificationVerifyCoordinatorDidComplete(_ coordinator: DeviceVerificationVerifyCoordinatorType) { - self.showVerified(animated: true) - } - - func deviceVerificationVerifyCoordinatorDidCancel(_ coordinator: DeviceVerificationVerifyCoordinatorType) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } -} - -extension DeviceVerificationCoordinator: DeviceVerificationVerifiedViewControllerDelegate { - func deviceVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: DeviceVerificationVerifiedViewController) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } - - func deviceVerificationVerifiedViewControllerDidCancel(_ viewController: DeviceVerificationVerifiedViewController) { - self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) - } -} diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift deleted file mode 100644 index c809533a3..000000000 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift +++ /dev/null @@ -1,101 +0,0 @@ -// File created from FlowTemplate -// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation - -@objc protocol DeviceVerificationCoordinatorBridgePresenterDelegate { - func deviceVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: DeviceVerificationCoordinatorBridgePresenter, otherUserId: String, otherDeviceId: String) -} - -/// DeviceVerificationCoordinatorBridgePresenter enables to start DeviceVerificationCoordinator from a view controller. -/// This bridge is used while waiting for global usage of coordinator pattern. -@objcMembers -final class DeviceVerificationCoordinatorBridgePresenter: NSObject { - - // MARK: - Properties - - // MARK: Private - - private let session: MXSession - private var coordinator: DeviceVerificationCoordinator? - - // MARK: Public - - weak var delegate: DeviceVerificationCoordinatorBridgePresenterDelegate? - - // MARK: - Setup - - init(session: MXSession) { - self.session = session - super.init() - } - - // MARK: - Public - - // NOTE: Default value feature is not compatible with Objective-C. - // func present(from viewController: UIViewController, animated: Bool) { - // self.present(from: viewController, animated: animated) - // } - - func present(from viewController: UIViewController, otherUserId: String, otherDeviceId: String, animated: Bool) { - - NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present from \(viewController)") - - let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId) - deviceVerificationCoordinator.delegate = self - viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil) - deviceVerificationCoordinator.start() - - self.coordinator = deviceVerificationCoordinator - } - - func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) { - - NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present incoming verification from \(viewController)") - - let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, incomingTransaction: incomingTransaction) - deviceVerificationCoordinator.delegate = self - viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil) - deviceVerificationCoordinator.start() - - self.coordinator = deviceVerificationCoordinator - } - - func dismiss(animated: Bool, completion: (() -> Void)?) { - guard let coordinator = self.coordinator else { - return - } - - NSLog("[DeviceVerificationCoordinatorBridgePresenter] Dismiss") - - coordinator.toPresentable().dismiss(animated: animated) { - self.coordinator = nil - - if let completion = completion { - completion() - } - } - } -} - -// MARK: - DeviceVerificationCoordinatorDelegate -extension DeviceVerificationCoordinatorBridgePresenter: DeviceVerificationCoordinatorDelegate { - func deviceVerificationCoordinatorDidComplete(_ coordinator: DeviceVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { - self.delegate?.deviceVerificationCoordinatorBridgePresenterDelegateDidComplete(self, otherUserId: otherUserId, otherDeviceId: otherDeviceId) - } -} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift deleted file mode 100644 index ece7185af..000000000 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift +++ /dev/null @@ -1,70 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation -import UIKit - -final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingCoordinatorType { - - // MARK: - Properties - - // MARK: Private - - private let session: MXSession - private var deviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType - private let deviceVerificationDataLoadingViewController: DeviceVerificationDataLoadingViewController - - // MARK: Public - - // Must be used only internally - var childCoordinators: [Coordinator] = [] - - weak var delegate: DeviceVerificationDataLoadingCoordinatorDelegate? - - // MARK: - Setup - - init(session: MXSession, otherUserId: String, otherDeviceId: String) { - self.session = session - - let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId) - let deviceVerificationDataLoadingViewController = DeviceVerificationDataLoadingViewController.instantiate(with: deviceVerificationDataLoadingViewModel) - self.deviceVerificationDataLoadingViewModel = deviceVerificationDataLoadingViewModel - self.deviceVerificationDataLoadingViewController = deviceVerificationDataLoadingViewController - } - - // MARK: - Public methods - - func start() { - self.deviceVerificationDataLoadingViewModel.coordinatorDelegate = self - } - - func toPresentable() -> UIViewController { - return self.deviceVerificationDataLoadingViewController - } -} - -// MARK: - DeviceVerificationDataLoadingViewModelCoordinatorDelegate -extension DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingViewModelCoordinatorDelegate { - func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) { - self.delegate?.deviceVerificationDataLoadingCoordinator(self, didLoadUser: user, device: device) - } - - func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType) { - self.delegate?.deviceVerificationDataLoadingCoordinatorDidCancel(self) - } -} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift deleted file mode 100644 index ed9ddb2fa..000000000 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift +++ /dev/null @@ -1,105 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation - -enum DeviceVerificationDataLoadingViewModelError: Error { - case unknown -} - -final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType { - - // MARK: - Properties - - // MARK: Private - - private let session: MXSession - private let otherUserId: String - private let otherDeviceId: String - - // MARK: Public - - weak var viewDelegate: DeviceVerificationDataLoadingViewModelViewDelegate? - weak var coordinatorDelegate: DeviceVerificationDataLoadingViewModelCoordinatorDelegate? - - // MARK: - Setup - - init(session: MXSession, otherUserId: String, otherDeviceId: String) { - self.session = session - self.otherUserId = otherUserId - self.otherDeviceId = otherDeviceId - } - - deinit { - } - - // MARK: - Public - - func process(viewAction: DeviceVerificationDataLoadingViewAction) { - switch viewAction { - case .loadData: - self.loadData() - case .cancel: - self.coordinatorDelegate?.deviceVerificationDataLoadingViewModelDidCancel(self) - } - } - - // MARK: - Private - - private func loadData() { - guard let crypto = self.session.crypto else { - self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) - NSLog("[DeviceVerificationDataLoadingViewModel] Error session.crypto is nil") - return - } - - if let otherUser = self.session.user(withUserId: otherUserId) { - - self.update(viewState: .loading) - - crypto.downloadKeys([self.otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap) in - guard let sself = self else { - return - } - - if let otherDevice = usersDevicesMap?.object(forDevice: sself.otherDeviceId, forUser: sself.otherUserId) { - sself.update(viewState: .loaded) - sself.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(sself, didLoadUser: otherUser, device: otherDevice) - } else { - sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) - } - - }, failure: { [weak self] (error) in - guard let sself = self else { - return - } - - let finalError = error ?? DeviceVerificationDataLoadingViewModelError.unknown - - sself.update(viewState: .error(finalError)) - }) - - } else { - self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) - } - } - - private func update(viewState: DeviceVerificationDataLoadingViewState) { - self.viewDelegate?.deviceVerificationDataLoadingViewModel(self, didUpdateViewState: viewState) - } -} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift deleted file mode 100644 index 0121c0028..000000000 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift +++ /dev/null @@ -1,37 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation - -protocol DeviceVerificationDataLoadingViewModelViewDelegate: class { - func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didUpdateViewState viewSate: DeviceVerificationDataLoadingViewState) -} - -protocol DeviceVerificationDataLoadingViewModelCoordinatorDelegate: class { - func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) - func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType) -} - -/// Protocol describing the view model used by `DeviceVerificationDataLoadingViewController` -protocol DeviceVerificationDataLoadingViewModelType { - - var viewDelegate: DeviceVerificationDataLoadingViewModelViewDelegate? { get set } - var coordinatorDelegate: DeviceVerificationDataLoadingViewModelCoordinatorDelegate? { get set } - - func process(viewAction: DeviceVerificationDataLoadingViewAction) -} diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift deleted file mode 100644 index 4b5c83580..000000000 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift +++ /dev/null @@ -1,71 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation -import UIKit - -final class DeviceVerificationVerifyCoordinator: DeviceVerificationVerifyCoordinatorType { - - // MARK: - Properties - - // MARK: Private - - private let session: MXSession - private var deviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModelType - private let deviceVerificationVerifyViewController: DeviceVerificationVerifyViewController - - // MARK: Public - - // Must be used only internally - var childCoordinators: [Coordinator] = [] - - weak var delegate: DeviceVerificationVerifyCoordinatorDelegate? - - // MARK: - Setup - - init(session: MXSession, transaction: MXSASTransaction) { - self.session = session - - let deviceVerificationVerifyViewModel = DeviceVerificationVerifyViewModel(session: self.session, transaction: transaction) - let deviceVerificationVerifyViewController = DeviceVerificationVerifyViewController.instantiate(with: deviceVerificationVerifyViewModel) - self.deviceVerificationVerifyViewModel = deviceVerificationVerifyViewModel - self.deviceVerificationVerifyViewController = deviceVerificationVerifyViewController - } - - // MARK: - Public methods - - func start() { - self.deviceVerificationVerifyViewModel.coordinatorDelegate = self - } - - func toPresentable() -> UIViewController { - return self.deviceVerificationVerifyViewController - } -} - -// MARK: - DeviceVerificationVerifyViewModelCoordinatorDelegate -extension DeviceVerificationVerifyCoordinator: DeviceVerificationVerifyViewModelCoordinatorDelegate { - - func deviceVerificationVerifyViewModelDidComplete(_ viewModel: DeviceVerificationVerifyViewModelType) { - self.delegate?.deviceVerificationVerifyCoordinatorDidComplete(self) - } - - func deviceVerificationVerifyViewModelDidCancel(_ viewModel: DeviceVerificationVerifyViewModelType) { - self.delegate?.deviceVerificationVerifyCoordinatorDidCancel(self) - } -} diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift deleted file mode 100644 index 8ea8aa449..000000000 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift +++ /dev/null @@ -1,40 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation - -protocol DeviceVerificationVerifyViewModelViewDelegate: class { - func deviceVerificationVerifyViewModel(_ viewModel: DeviceVerificationVerifyViewModelType, didUpdateViewState viewSate: DeviceVerificationVerifyViewState) -} - -protocol DeviceVerificationVerifyViewModelCoordinatorDelegate: class { - func deviceVerificationVerifyViewModelDidComplete(_ viewModel: DeviceVerificationVerifyViewModelType) - func deviceVerificationVerifyViewModelDidCancel(_ viewModel: DeviceVerificationVerifyViewModelType) -} - -/// Protocol describing the view model used by `DeviceVerificationVerifyViewController` -protocol DeviceVerificationVerifyViewModelType { - - var viewDelegate: DeviceVerificationVerifyViewModelViewDelegate? { get set } - var coordinatorDelegate: DeviceVerificationVerifyViewModelCoordinatorDelegate? { get set } - - func process(viewAction: DeviceVerificationVerifyViewAction) - - var emojis: [MXEmojiRepresentation]? { get set } - var decimal: String? { get set } -} diff --git a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m index 4d858639c..1043f3616 100644 --- a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m +++ b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m @@ -22,9 +22,9 @@ #import "AppDelegate.h" -@interface EncryptionInfoView() +@interface EncryptionInfoView() { - DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter; + KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; } @end @@ -54,16 +54,16 @@ - (void)onButtonPressed:(id)sender { UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; - if (sender == self.verifyButton && self.mxDeviceInfo.verified != MXDeviceVerified + if (sender == self.verifyButton && self.mxDeviceInfo.trustLevel.localVerificationStatus != MXDeviceVerified && self.mxDeviceInfo && rootViewController) { // Redirect to the interactive device verification flow - deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:self.mxSession]; - deviceVerificationCoordinatorBridgePresenter.delegate = self; + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:self.mxSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; // Show it on the root view controller - [deviceVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:self.mxDeviceInfo.userId otherDeviceId:self.mxDeviceInfo.deviceId animated:YES]; + [keyVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:self.mxDeviceInfo.userId otherDeviceId:self.mxDeviceInfo.deviceId animated:YES]; } else { @@ -71,11 +71,21 @@ } } -- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} - [deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; - deviceVerificationCoordinatorBridgePresenter = nil; +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ + [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + keyVerificationCoordinatorBridgePresenter = nil; + // Eject like MXKEncryptionInfoView does [self removeFromSuperview]; } diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.m b/Riot/Modules/Home/Views/RoomCollectionViewCell.m index 5c6ed3aa3..34ccbb861 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.m +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.m @@ -159,7 +159,15 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; self.directRoomBorderView.hidden = !roomCellData.roomSummary.room.isDirect; - self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; + if (roomCellData.roomSummary.isEncrypted) + { + self.encryptedRoomIcon.hidden = NO; + self.encryptedRoomIcon.image = [EncryptionTrustLevelBadgeImageHelper roomBadgeImageFor:roomCellData.roomSummary.roomEncryptionTrustLevel]; + } + else + { + self.encryptedRoomIcon.hidden = YES; + } [roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar]; } diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib index b0c964fa1..45366f0ff 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib @@ -1,12 +1,11 @@ - + - - + @@ -39,7 +38,7 @@ - @@ -103,18 +101,19 @@ + - + @@ -131,6 +130,6 @@ - + diff --git a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift index 3912c1715..88ff6d375 100644 --- a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift +++ b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift @@ -43,16 +43,16 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { // MARK: - Public func start() { - + let rootCoordinator: Coordinator & Presentable - // Check if a passphrase has been set for given backup - if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { - rootCoordinator = self.createRecoverFromPassphraseCoordinator() + // Check if we have the private key locally + if self.session.crypto.backup.hasPrivateKeyInCryptoStore { + rootCoordinator = self.createRecoverFromPrivateKeyCoordinator() } else { - rootCoordinator = self.createRecoverFromRecoveryKeyCoordinator() + rootCoordinator = self.createRecoverWithUserInteractionCoordinator() } - + rootCoordinator.start() self.add(childCoordinator: rootCoordinator) @@ -66,6 +66,24 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { // MARK: - Private + private func createRecoverWithUserInteractionCoordinator() -> Coordinator & Presentable { + let coordinator: Coordinator & Presentable + + // Check if a passphrase has been set for given backup + if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { + coordinator = self.createRecoverFromPassphraseCoordinator() + } else { + coordinator = self.createRecoverFromRecoveryKeyCoordinator() + } + return coordinator + } + + private func createRecoverFromPrivateKeyCoordinator() -> KeyBackupRecoverFromPrivateKeyCoordinator { + let coordinator = KeyBackupRecoverFromPrivateKeyCoordinator(keyBackup: self.session.crypto.backup, keyBackupVersion: self.keyBackupVersion) + coordinator.delegate = self + return coordinator + } + private func createRecoverFromPassphraseCoordinator() -> KeyBackupRecoverFromPassphraseCoordinator { let coordinator = KeyBackupRecoverFromPassphraseCoordinator(keyBackup: self.session.crypto.backup, keyBackupVersion: self.keyBackupVersion) coordinator.delegate = self @@ -95,6 +113,33 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { keyBackupRecoverSuccessViewController.delegate = self self.navigationRouter.push(keyBackupRecoverSuccessViewController, animated: true, popCompletion: nil) } + + private func showRecoverFallback() { + let coordinator = self.createRecoverWithUserInteractionCoordinator() + self.add(childCoordinator: coordinator) + + // Skip the previously displayed KeyBackupRecoverFromPrivateKeyCoordinator in the navigation stack + self.navigationRouter.setRootModule(coordinator) + + coordinator.start() + } +} + +// MARK: - KeyBackupRecoverFromPassphraseCoordinatorDelegate +extension KeyBackupRecoverCoordinator: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate { + + func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { + self.showRecoverSuccess() + } + + func keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { + // The private key did not work. Ask the user to enter their passphrase or recovery key + self.showRecoverFallback() + } + + func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { + self.delegate?.keyBackupRecoverCoordinatorDidCancel(self) + } } // MARK: - KeyBackupRecoverFromPassphraseCoordinatorDelegate @@ -125,7 +170,7 @@ extension KeyBackupRecoverCoordinator: KeyBackupRecoverFromRecoveryKeyCoordinato // MARK: - KeyBackupRecoverSuccessViewControllerDelegate extension KeyBackupRecoverCoordinator: KeyBackupRecoverSuccessViewControllerDelegate { - func KeyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) { + func keyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) { self.delegate?.keyBackupRecoverCoordinatorDidRecover(self) } } diff --git a/Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift b/Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift new file mode 100644 index 000000000..1ef8aab16 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyBackup/Recover/Loading KeyBackupRecoverDataLoading +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyBackupRecoverDataLoadingViewModelViewDelegate: class { + func keyBackupRecoverDataLoadingViewModel(_ viewModel: KeyBackupRecoverDataLoadingViewModelType, didUpdateViewState viewSate: KeyBackupRecoverDataLoadingViewState) +} + +protocol KeyBackupRecoverDataLoadingViewModelCoordinatorDelegate: class { + func keyBackupRecoverDataLoadingViewModelDidRecover(_ viewModel: KeyBackupRecoverDataLoadingViewModelType) + func keyBackupRecoverDataLoadingViewModelDidCancel(_ viewModel: KeyBackupRecoverDataLoadingViewModelType) +} + +/// Protocol describing the view model used by `KeyBackupRecoverDataLoadingViewController` +protocol KeyBackupRecoverDataLoadingViewModelType { + + var viewDelegate: KeyBackupRecoverDataLoadingViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyBackupRecoverDataLoadingViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyBackupRecoverDataLoadingViewAction) +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift new file mode 100644 index 000000000..f5b2d3831 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift @@ -0,0 +1,72 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyBackupRecoverFromPrivateKeyCoordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private var keyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivateKeyViewModelType + private let keyBackupRecoverFromPrivateKeyViewController: KeyBackupRecoverFromPrivateKeyViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate? + + // MARK: - Setup + + init(keyBackup: MXKeyBackup, keyBackupVersion: MXKeyBackupVersion) { + + let keyBackupRecoverFromPrivateKeyViewModel = KeyBackupRecoverFromPrivateKeyViewModel(keyBackup: keyBackup, keyBackupVersion: keyBackupVersion) + let keyBackupRecoverFromPrivateKeyViewController = KeyBackupRecoverFromPrivateKeyViewController.instantiate(with: keyBackupRecoverFromPrivateKeyViewModel) + self.keyBackupRecoverFromPrivateKeyViewModel = keyBackupRecoverFromPrivateKeyViewModel + self.keyBackupRecoverFromPrivateKeyViewController = keyBackupRecoverFromPrivateKeyViewController + } + + // MARK: - Public methods + + func start() { + self.keyBackupRecoverFromPrivateKeyViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyBackupRecoverFromPrivateKeyViewController + } +} + +// MARK: - KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate +extension KeyBackupRecoverFromPrivateKeyCoordinator: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate { + func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { + self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(self) + } + + func keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { + self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(self) + } + + func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { + self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift new file mode 100644 index 000000000..aab042ba5 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift @@ -0,0 +1,30 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyBackupRecoverFromPrivateKeyCoordinatorDelegate: class { + func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) + func keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) + func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) +} + +/// `KeyBackupRecoverFromPrivateKeyCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyBackupRecoverFromPrivateKeyCoordinatorType: Coordinator, Presentable { + var delegate: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift new file mode 100644 index 000000000..5ad5a61bf --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift @@ -0,0 +1,25 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyBackupRecoverFromPrivateKeyViewController view actions exposed to view model +enum KeyBackupRecoverFromPrivateKeyViewAction { + case recover + case cancel +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard new file mode 100644 index 000000000..1c8ba341c --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift new file mode 100644 index 000000000..1aaf96e62 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift @@ -0,0 +1,158 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class KeyBackupRecoverFromPrivateKeyViewController: UIViewController { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var shieldImageView: UIImageView! + + @IBOutlet private weak var informationLabel: UILabel! + + // MARK: Private + + private var viewModel: KeyBackupRecoverFromPrivateKeyViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) -> KeyBackupRecoverFromPrivateKeyViewController { + let viewController = StoryboardScene.KeyBackupRecoverFromPrivateKeyViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.keyBackupRecoverTitle + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .recover) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + + // MARK: - Private + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + let shieldImage = Asset.Images.keyBackupLogo.image.withRenderingMode(.alwaysTemplate) + self.shieldImageView.image = shieldImage + + self.informationLabel.text = VectorL10n.keyBackupRecoverFromPrivateKeyInfo + } + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.shieldImageView.tintColor = theme.textPrimaryColor + + self.informationLabel.textColor = theme.textPrimaryColor + } + + private func render(viewState: KeyBackupRecoverFromPrivateKeyViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded: + self.renderLoaded() + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + + // MARK: - Actions + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyBackupRecoverFromPrivateKeyViewModelViewDelegate +extension KeyBackupRecoverFromPrivateKeyViewController: KeyBackupRecoverFromPrivateKeyViewModelViewDelegate { + + func keyBackupRecoverFromPrivateKeyViewModel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromPrivateKeyViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift new file mode 100644 index 000000000..408a404df --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift @@ -0,0 +1,98 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivateKeyViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let keyBackup: MXKeyBackup + private var currentHTTPOperation: MXHTTPOperation? + private let keyBackupVersion: MXKeyBackupVersion + + // MARK: Public + + weak var viewDelegate: KeyBackupRecoverFromPrivateKeyViewModelViewDelegate? + weak var coordinatorDelegate: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(keyBackup: MXKeyBackup, keyBackupVersion: MXKeyBackupVersion) { + self.keyBackup = keyBackup + self.keyBackupVersion = keyBackupVersion + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: KeyBackupRecoverFromPrivateKeyViewAction) { + switch viewAction { + case .recover: + self.recoverWithPrivateKey() + case .cancel: + self.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func recoverWithPrivateKey() { + + self.update(viewState: .loading) + + self.currentHTTPOperation = keyBackup.restore(usingPrivateKeyKeyBackup: keyBackupVersion, room: nil, session: nil, success: { [weak self] (_, _) in + guard let self = self else { + return + } + + // Trust on decrypt + self.currentHTTPOperation = self.keyBackup.trust(self.keyBackupVersion, trust: true, success: { [weak self] () in + guard let self = self else { + return + } + + self.update(viewState: .loaded) + self.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidRecover(self) + + }, failure: { [weak self] error in + self?.update(viewState: .error(error)) + }) + + }, failure: { [weak self] error in + guard let self = self else { + return + } + + if (error as NSError).domain == MXKeyBackupErrorDomain + && (error as NSError).code == Int(MXKeyBackupErrorInvalidOrMissingLocalPrivateKey.rawValue) { + self.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(self) + } else { + self.update(viewState: .error(error)) + } + }) + } + + private func update(viewState: KeyBackupRecoverFromPrivateKeyViewState) { + self.viewDelegate?.keyBackupRecoverFromPrivateKeyViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift new file mode 100644 index 000000000..1188e0560 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift @@ -0,0 +1,38 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyBackupRecoverFromPrivateKeyViewModelViewDelegate: class { + func keyBackupRecoverFromPrivateKeyViewModel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromPrivateKeyViewState) +} + +protocol KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate: class { + func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) + func keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) + func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) +} + +/// Protocol describing the view model used by `KeyBackupRecoverFromPrivateKeyViewController` +protocol KeyBackupRecoverFromPrivateKeyViewModelType { + + var viewDelegate: KeyBackupRecoverFromPrivateKeyViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyBackupRecoverFromPrivateKeyViewAction) +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift new file mode 100644 index 000000000..bdd417853 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyBackupRecoverFromPrivateKeyViewController view state +enum KeyBackupRecoverFromPrivateKeyViewState { + case loading + case loaded + case error(Error) +} diff --git a/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift b/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift index 8d126897d..dd5a834da 100644 --- a/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift +++ b/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift @@ -17,7 +17,7 @@ import UIKit protocol KeyBackupRecoverSuccessViewControllerDelegate: class { - func KeyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) + func keyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) } final class KeyBackupRecoverSuccessViewController: UIViewController { @@ -115,6 +115,6 @@ final class KeyBackupRecoverSuccessViewController: UIViewController { } @IBAction private func doneButtonAction(_ sender: Any) { - self.delegate?.KeyBackupRecoverSuccessViewControllerDidTapDone(self) + self.delegate?.keyBackupRecoverSuccessViewControllerDidTapDone(self) } } diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift new file mode 100644 index 000000000..aaf890f89 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift @@ -0,0 +1,410 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let navigationRouter: NavigationRouterType + private let session: MXSession + private let verificationFlow: KeyVerificationFlow + private let verificationKind: KeyVerificationKind + + private var otherUserId: String { + + let otherUserId: String + + switch self.verificationFlow { + case .verifyUser(let roomMember): + otherUserId = roomMember.userId + case .verifyDevice(let userId, _): + otherUserId = userId + case .incomingRequest(let incomingKeyVerificationRequest): + otherUserId = incomingKeyVerificationRequest.otherUser + case .incomingSASTransaction(let incomingSASTransaction): + otherUserId = incomingSASTransaction.otherUserId + case .completeSecurity: + otherUserId = self.session.myUser.userId + } + + return otherUserId + } + + private var otherDeviceId: String? { + + let otherDeviceId: String? + + switch self.verificationFlow { + case .verifyUser: + otherDeviceId = nil + case .verifyDevice(_, let deviceId): + otherDeviceId = deviceId + case .incomingRequest(let incomingKeyVerificationRequest): + otherDeviceId = incomingKeyVerificationRequest.otherDevice + case .incomingSASTransaction(let incomingSASTransaction): + otherDeviceId = incomingSASTransaction.otherDeviceId + case .completeSecurity: + otherDeviceId = nil + } + + return otherDeviceId + } + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationCoordinatorDelegate? + + // MARK: - Setup + + /// Creates a key verification coordinator. + /// + /// - Parameters: + /// - session: The MXSession. + /// - flow: The wanted key verification flow. + /// - navigationRouter: Existing NavigationRouter from which present the flow (optional). + init(session: MXSession, flow: KeyVerificationFlow, navigationRouter: NavigationRouterType? = nil) { + self.navigationRouter = navigationRouter ?? NavigationRouter(navigationController: RiotNavigationController()) + + self.session = session + self.verificationFlow = flow + + if case .verifyUser = flow { + self.verificationKind = .user + } else { + self.verificationKind = .device + } + } + + // MARK: - Public methods + + func start() { + let rootCoordinator: Coordinator & Presentable + + switch self.verificationFlow { + case .verifyUser(let roomMember): + rootCoordinator = self.createUserVerificationStartCoordinator(with: roomMember) + case .verifyDevice(let userId, let deviceId): + if userId == self.session.myUser.userId { + rootCoordinator = self.createSelfVerificationCoordinator(otherDeviceId: deviceId) + } else { + rootCoordinator = self.createDataLoadingScreenCoordinator(otherUserId: userId, otherDeviceId: deviceId) + } + case .incomingRequest(let incomingKeyVerificationRequest): + rootCoordinator = self.createDataLoadingScreenCoordinator(with: incomingKeyVerificationRequest) + case .incomingSASTransaction(let incomingSASTransaction): + rootCoordinator = self.createDataLoadingScreenCoordinator(otherUserId: incomingSASTransaction.otherUserId, otherDeviceId: incomingSASTransaction.otherDeviceId) + case .completeSecurity: + rootCoordinator = self.createCompleteSecurityCoordinator() + } + + rootCoordinator.start() + + self.add(childCoordinator: rootCoordinator) + + if self.navigationRouter.modules.isEmpty == false { + self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + self?.remove(childCoordinator: rootCoordinator) + }) + } else { + self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + self?.remove(childCoordinator: rootCoordinator) + } + } + } + + func toPresentable() -> UIViewController { + return self.navigationRouter.toPresentable() + } + + // MARK: - Private methods + + private func didComplete() { + self.delegate?.keyVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId ?? "") + } + + private func didCancel() { + self.delegate?.keyVerificationCoordinatorDidCancel(self) + } + + private func createCompleteSecurityCoordinator() -> KeyVerificationSelfVerifyWaitCoordinatorType { + let coordinator = KeyVerificationSelfVerifyWaitCoordinator(session: self.session) + coordinator.delegate = self + coordinator.start() + + return coordinator + } + + private func createSelfVerificationCoordinator(otherDeviceId: String) -> KeyVerificationSelfVerifyStartCoordinator { + let coordinator = KeyVerificationSelfVerifyStartCoordinator(session: self.session, otherDeviceId: otherDeviceId) + coordinator.delegate = self + coordinator.start() + + return coordinator + } + + private func createDataLoadingScreenCoordinator(otherUserId: String, otherDeviceId: String) -> KeyVerificationDataLoadingCoordinator { + let coordinator = KeyVerificationDataLoadingCoordinator(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId) + coordinator.delegate = self + coordinator.start() + + return coordinator + } + + private func createDataLoadingScreenCoordinator(with keyVerificationRequest: MXKeyVerificationRequest) -> KeyVerificationDataLoadingCoordinator { + let coordinator = KeyVerificationDataLoadingCoordinator(session: self.session, incomingKeyVerificationRequest: keyVerificationRequest) + coordinator.delegate = self + coordinator.start() + + return coordinator + } + + private func createUserVerificationStartCoordinator(with roomMember: MXRoomMember) -> UserVerificationStartCoordinator { + let coordinator = UserVerificationStartCoordinator(session: self.session, roomMember: roomMember) + coordinator.delegate = self + coordinator.start() + + return coordinator + } + + private func showStart(otherUser: MXUser, otherDevice: MXDeviceInfo) { + let coordinator = DeviceVerificationStartCoordinator(session: self.session, otherUser: otherUser, otherDevice: otherDevice) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.setRootModule(coordinator) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } + + private func showIncoming(otherUser: MXUser, transaction: MXIncomingSASTransaction) { + let coordinator = DeviceVerificationIncomingCoordinator(session: self.session, otherUser: otherUser, transaction: transaction) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.setRootModule(coordinator) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } + + private func showVerifyBySAS(transaction: MXSASTransaction, animated: Bool) { + let coordinator = KeyVerificationVerifyBySASCoordinator(session: self.session, transaction: transaction, verificationKind: self.verificationKind) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator, animated: animated) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } + + private func showVerifyByScanning(keyVerificationRequest: MXKeyVerificationRequest, animated: Bool) { + let coordinator = KeyVerificationVerifyByScanningCoordinator(session: self.session, keyVerificationRequest: keyVerificationRequest) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator, animated: animated) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } + + private func showScanConfirmation(for transaction: MXQRCodeTransaction, codeScanning: KeyVerificationScanning, animated: Bool) { + let coordinator = KeyVerificationScanConfirmationCoordinator(session: self.session, transaction: transaction, codeScanning: codeScanning, verificationKind: self.verificationKind) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator, animated: animated) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } + + private func showVerified(animated: Bool) { + let viewController = KeyVerificationVerifiedViewController.instantiate(with: self.verificationKind) + viewController.delegate = self + self.navigationRouter.setRootModule(viewController) + } +} + +// MARK: - KeyVerificationDataLoadingCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationDataLoadingCoordinatorDelegate { + + func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) { + self.showVerifyByScanning(keyVerificationRequest: keyVerificationRequest, animated: true) + } + + func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) { + + if case .incomingSASTransaction(let incomingTransaction) = self.verificationFlow { + self.showIncoming(otherUser: user, transaction: incomingTransaction) + } else { + self.showStart(otherUser: user, otherDevice: device) + } + } + + func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXKeyVerificationTransaction) { + + if let sasTransaction = transaction as? MXSASTransaction { + self.showVerifyBySAS(transaction: sasTransaction, animated: true) + } else { + NSLog("[KeyVerificationCoordinator] Transaction \(transaction) is not supported") + self.didCancel() + } + } + + func keyVerificationDataLoadingCoordinatorDidCancel(_ coordinator: KeyVerificationDataLoadingCoordinatorType) { + self.didCancel() + } +} + +// MARK: - DeviceVerificationStartCoordinatorDelegate +extension KeyVerificationCoordinator: DeviceVerificationStartCoordinatorDelegate { + func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { + self.showVerifyBySAS(transaction: transaction, animated: true) + } + + func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) { + self.didCancel() + } + + func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) { + self.didCancel() + } +} + +// MARK: - DeviceVerificationIncomingCoordinatorDelegate +extension KeyVerificationCoordinator: DeviceVerificationIncomingCoordinatorDelegate { + func deviceVerificationIncomingCoordinator(_ coordinator: DeviceVerificationIncomingCoordinatorType, didAcceptTransaction transaction: MXSASTransaction) { + self.showVerifyBySAS(transaction: transaction, animated: true) + } + + func deviceVerificationIncomingCoordinatorDidCancel(_ coordinator: DeviceVerificationIncomingCoordinatorType) { + self.didCancel() + } +} + +// MARK: - KeyVerificationVerifyBySASCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationVerifyBySASCoordinatorDelegate { + func keyVerificationVerifyBySASCoordinatorDidComplete(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) { + self.showVerified(animated: true) + } + + func keyVerificationVerifyBySASCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) { + self.didCancel() + } +} + +// MARK: - KeyVerificationVerifiedViewControllerDelegate +extension KeyVerificationCoordinator: KeyVerificationVerifiedViewControllerDelegate { + func keyVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: KeyVerificationVerifiedViewController) { + self.didComplete() + } + + func keyVerificationVerifiedViewControllerDidCancel(_ viewController: KeyVerificationVerifiedViewController) { + self.didCancel() + } +} + +// MARK: - UserVerificationStartCoordinatorDelegate +extension KeyVerificationCoordinator: UserVerificationStartCoordinatorDelegate { + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) { + self.showVerifyByScanning(keyVerificationRequest: request, animated: true) + } + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { + self.showVerifyBySAS(transaction: transaction, animated: true) + } + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) { + self.didCancel() + } + + func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType) { + self.didCancel() + } +} + +// MARK: - KeyVerificationVerifyByScanningCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationVerifyByScanningCoordinatorDelegate { + + func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) { + self.didCancel() + } + + func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) { + self.showVerified(animated: true) + } + + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) { + self.showScanConfirmation(for: transaction, codeScanning: .scannedOtherQRCode(qrCodeData), animated: true) + } + + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) { + self.showScanConfirmation(for: transaction, codeScanning: .myQRCodeScanned, animated: true) + } + + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) { + self.showVerifyBySAS(transaction: transaction, animated: true) + } +} + +// MARK: - KeyVerificationSelfVerifyStartCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationSelfVerifyStartCoordinatorDelegate { + + func keyVerificationSelfVerifyStartCoordinator(_ coordinator: KeyVerificationSelfVerifyStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) { + self.showVerifyByScanning(keyVerificationRequest: request, animated: true) + } + + func keyVerificationSelfVerifyStartCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyStartCoordinatorType) { + self.didCancel() + } +} + +// MARK: - KeyVerificationSelfVerifyWaitCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationSelfVerifyWaitCoordinatorDelegate { + func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) { + self.showVerifyByScanning(keyVerificationRequest: keyVerificationRequest, animated: true) + } + + func keyVerificationSelfVerifyWaitCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) { + self.didCancel() + } +} + +// MARK: - KeyVerificationScanConfirmationCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationScanConfirmationCoordinatorDelegate { + + func keyVerificationScanConfirmationCoordinatorDidComplete(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) { + self.showVerified(animated: true) + } + + func keyVerificationScanConfirmationCoordinatorDidCancel(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) { + self.didCancel() + } +} diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..e6bd0a697 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift @@ -0,0 +1,144 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objc protocol KeyVerificationCoordinatorBridgePresenterDelegate { + func keyVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: KeyVerificationCoordinatorBridgePresenter, otherUserId: String, otherDeviceId: String) + func keyVerificationCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: KeyVerificationCoordinatorBridgePresenter) +} + +/// KeyVerificationCoordinatorBridgePresenter enables to start KeyVerificationCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class KeyVerificationCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + let session: MXSession + private var coordinator: KeyVerificationCoordinator? + + // MARK: Public + + weak var delegate: KeyVerificationCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(session: MXSession) { + self.session = session + super.init() + } + + // MARK: - Public + + // NOTE: Default value feature is not compatible with Objective-C. + // func present(from viewController: UIViewController, animated: Bool) { + // self.present(from: viewController, animated: animated) + // } + + func present(from viewController: UIViewController, otherUserId: String, otherDeviceId: String, animated: Bool) { + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Present from \(viewController)") + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyDevice(userId: otherUserId, deviceId: otherDeviceId)) + self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated) + } + + func present(from viewController: UIViewController, roomMember: MXRoomMember, animated: Bool) { + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Present from \(viewController)") + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyUser(roomMember)) + self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated) + } + + func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) { + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Present incoming verification from \(viewController)") + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .incomingSASTransaction(incomingTransaction)) + self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated) + } + + func present(from viewController: UIViewController, incomingKeyVerificationRequest: MXKeyVerificationRequest, animated: Bool) { + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Present incoming key verification request from \(viewController)") + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .incomingRequest(incomingKeyVerificationRequest)) + self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated) + } + + func presentCompleteSecurity(from viewController: UIViewController, animated: Bool) { + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Present complete security from \(viewController)") + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity) + self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated) + } + + func pushCompleteSecurity(from navigationController: UINavigationController, animated: Bool) { + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Push complete security from \(navigationController)") + + let navigationRouter = NavigationRouter(navigationController: navigationController) + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity, navigationRouter: navigationRouter) + keyVerificationCoordinator.delegate = self + keyVerificationCoordinator.start() // Will trigger view controller push + + self.coordinator = keyVerificationCoordinator + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + guard let coordinator = self.coordinator else { + return + } + + NSLog("[KeyVerificationCoordinatorBridgePresenter] Dismiss") + + coordinator.toPresentable().dismiss(animated: animated) { + self.coordinator = nil + + if let completion = completion { + completion() + } + } + } + + private func present(coordinator keyVerificationCoordinator: KeyVerificationCoordinator, from viewController: UIViewController, animated: Bool) { + keyVerificationCoordinator.delegate = self + viewController.present(keyVerificationCoordinator.toPresentable(), animated: animated, completion: nil) + keyVerificationCoordinator.start() + + self.coordinator = keyVerificationCoordinator + } +} + +// MARK: - KeyVerificationCoordinatorDelegate +extension KeyVerificationCoordinatorBridgePresenter: KeyVerificationCoordinatorDelegate { + + func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { + self.delegate?.keyVerificationCoordinatorBridgePresenterDelegateDidComplete(self, otherUserId: otherUserId, otherDeviceId: otherDeviceId) + } + + func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) { + self.delegate?.keyVerificationCoordinatorBridgePresenterDelegateDidCancel(self) + } +} diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift similarity index 57% rename from Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorType.swift rename to Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift index c1f616da8..901d6b11d 100644 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift @@ -18,11 +18,12 @@ import Foundation -protocol DeviceVerificationCoordinatorDelegate: class { - func deviceVerificationCoordinatorDidComplete(_ coordinator: DeviceVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) +protocol KeyVerificationCoordinatorDelegate: class { + func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) + func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) } -/// `DeviceVerificationCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. -protocol DeviceVerificationCoordinatorType: Coordinator, Presentable { - var delegate: DeviceVerificationCoordinatorDelegate? { get } +/// `KeyVerificationCoordinatorType` is a protocol describing a Coordinator that handle key verification navigation flow. +protocol KeyVerificationCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationCoordinatorDelegate? { get } } diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift new file mode 100644 index 000000000..008b4f0ed --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift @@ -0,0 +1,32 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// Key verification flow used by KeyVerificationCoordinator +/// +/// - verifyUser: Start a user verification. +/// - verifyDevice: Start a verification of another device. +/// - completeSecurity: Wait to be verified by another session after login on a new device. +/// - incomingRequest: Manage an incoming key verification request. +/// - incomingSASTransaction: Manage an incoming SAS verification transaction +enum KeyVerificationFlow { + case verifyUser(_ roomMember: MXRoomMember) + case verifyDevice(userId: String, deviceId: String) + case completeSecurity + case incomingRequest(_ request: MXKeyVerificationRequest) + case incomingSASTransaction(_ transaction: MXIncomingSASTransaction) +} diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationKind.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationKind.swift new file mode 100644 index 000000000..d9d8f29fe --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationKind.swift @@ -0,0 +1,21 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import Foundation + +enum KeyVerificationKind { + case device + case user +} diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift new file mode 100644 index 000000000..4df2095f3 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift @@ -0,0 +1,40 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyVerificationService { + + private let cameraAccessManager: CameraAccessManager + + init() { + self.cameraAccessManager = CameraAccessManager() + } + + func supportedKeyVerificationMethods() -> [String] { + var supportedMethods: [String] = [ + MXKeyVerificationMethodSAS, + MXKeyVerificationMethodQRCodeShow, + MXKeyVerificationMethodReciprocate + ] + + if self.cameraAccessManager.isCameraAvailable { + supportedMethods.append(MXKeyVerificationMethodQRCodeScan) + } + + return supportedMethods + } +} diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinator.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinator.swift new file mode 100644 index 000000000..fc20b30b1 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinator.swift @@ -0,0 +1,83 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyVerificationDataLoadingCoordinator: KeyVerificationDataLoadingCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private var keyVerificationDataLoadingViewModel: KeyVerificationDataLoadingViewModelType + private let keyVerificationDataLoadingViewController: KeyVerificationDataLoadingViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationDataLoadingCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, otherUserId: String, otherDeviceId: String) { + let keyVerificationDataLoadingViewModel = KeyVerificationDataLoadingViewModel(session: session, otherUserId: otherUserId, otherDeviceId: otherDeviceId) + let keyVerificationDataLoadingViewController = KeyVerificationDataLoadingViewController.instantiate(with: keyVerificationDataLoadingViewModel) + self.keyVerificationDataLoadingViewModel = keyVerificationDataLoadingViewModel + self.keyVerificationDataLoadingViewController = keyVerificationDataLoadingViewController + } + + init(session: MXSession, incomingKeyVerificationRequest: MXKeyVerificationRequest) { + let keyVerificationDataLoadingViewModel = KeyVerificationDataLoadingViewModel(session: session, keyVerificationRequest: incomingKeyVerificationRequest) + let keyVerificationDataLoadingViewController = KeyVerificationDataLoadingViewController.instantiate(with: keyVerificationDataLoadingViewModel) + self.keyVerificationDataLoadingViewModel = keyVerificationDataLoadingViewModel + self.keyVerificationDataLoadingViewController = keyVerificationDataLoadingViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationDataLoadingViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationDataLoadingViewController + } +} + +// MARK: - KeyVerificationDataLoadingViewModelCoordinatorDelegate +extension KeyVerificationDataLoadingCoordinator: KeyVerificationDataLoadingViewModelCoordinatorDelegate { + + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) { + self.delegate?.keyVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequest: keyVerificationRequest) + } + + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction) { + self.delegate?.keyVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequestWithTransaction: transaction) + } + + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) { + self.delegate?.keyVerificationDataLoadingCoordinator(self, didLoadUser: user, device: device) + } + + func keyVerificationDataLoadingViewModelDidCancel(_ viewModel: KeyVerificationDataLoadingViewModelType) { + self.delegate?.keyVerificationDataLoadingCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift new file mode 100644 index 000000000..8a7ba1dae --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingCoordinatorType.swift @@ -0,0 +1,31 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationDataLoadingCoordinatorDelegate: class { + func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) + func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXKeyVerificationTransaction) + func keyVerificationDataLoadingCoordinator(_ coordinator: KeyVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) + func keyVerificationDataLoadingCoordinatorDidCancel(_ coordinator: KeyVerificationDataLoadingCoordinatorType) +} + +/// `KeyVerificationDataLoadingCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationDataLoadingCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationDataLoadingCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewAction.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewAction.swift similarity index 85% rename from Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewAction.swift rename to Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewAction.swift index 4c4d86459..c36505141 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewAction.swift +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewAction.swift @@ -18,8 +18,8 @@ import Foundation -/// DeviceVerificationDataLoadingViewController view actions exposed to view model -enum DeviceVerificationDataLoadingViewAction { +/// KeyVerificationDataLoadingViewController view actions exposed to view model +enum KeyVerificationDataLoadingViewAction { case loadData case cancel } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.storyboard b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewController.storyboard similarity index 96% rename from Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.storyboard rename to Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewController.storyboard index 5140d7bfd..e572e88e1 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewController.storyboard @@ -10,10 +10,10 @@ - + - + diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewController.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewController.swift new file mode 100644 index 000000000..ecf44f34b --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewController.swift @@ -0,0 +1,177 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class KeyVerificationDataLoadingViewController: UIViewController { + + // MARK: - Properties + + // MARK: Outlets + + // MARK: Private + + private var viewModel: KeyVerificationDataLoadingViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyVerificationDataLoadingViewModelType) -> KeyVerificationDataLoadingViewController { + let viewController = StoryboardScene.KeyVerificationDataLoadingViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.deviceVerificationTitle + self.vc_removeBackTitle() + + self.setupViews() + + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } + + private func render(viewState: KeyVerificationDataLoadingViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded: + self.renderLoaded() + case .error(let error): + self.render(error: error) + case .errorMessage(let message): + self.renderError(message: message) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + + private func render(error: Error) { + + var shouldDisplayError = true + var message: String? + + switch error { + case KeyVerificationDataLoadingViewModelError.transactionCancelled: + message = VectorL10n.deviceVerificationCancelled + case KeyVerificationDataLoadingViewModelError.transactionCancelledByMe(reason: let reason): + if reason.value != MXTransactionCancelCode.user().value { + message = VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable) + } else { + shouldDisplayError = false + } + default: + break + } + + if shouldDisplayError { + + let completion = { + self.viewModel.process(viewAction: .cancel) + } + + if let message = message { + self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: completion) + } else { + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: completion) + } + } + } + + private func renderError(message: String) { + self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: { + self.viewModel.process(viewAction: .cancel) + }) + } + + // MARK: - Actions + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyVerificationDataLoadingViewModelViewDelegate +extension KeyVerificationDataLoadingViewController: KeyVerificationDataLoadingViewModelViewDelegate { + + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didUpdateViewState viewSate: KeyVerificationDataLoadingViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModel.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModel.swift new file mode 100644 index 000000000..401d07447 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModel.swift @@ -0,0 +1,150 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum KeyVerificationDataLoadingViewModelError: Error { + case unknown + case transactionCancelled + case transactionCancelledByMe(reason: MXTransactionCancelCode) +} + +final class KeyVerificationDataLoadingViewModel: KeyVerificationDataLoadingViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let otherUserId: String? + private let otherDeviceId: String? + private let keyVerificationService = KeyVerificationService() + + private let keyVerificationRequest: MXKeyVerificationRequest? + + private var currentOperation: MXHTTPOperation? + + // MARK: Public + + weak var viewDelegate: KeyVerificationDataLoadingViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationDataLoadingViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, otherUserId: String, otherDeviceId: String) { + self.session = session + self.otherUserId = otherUserId + self.otherDeviceId = otherDeviceId + self.keyVerificationRequest = nil + } + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.otherUserId = nil + self.otherDeviceId = nil + self.keyVerificationRequest = keyVerificationRequest + } + + deinit { + self.currentOperation?.cancel() + } + + // MARK: - Public + + func process(viewAction: KeyVerificationDataLoadingViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .cancel: + self.coordinatorDelegate?.keyVerificationDataLoadingViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + if let keyVerificationRequest = self.keyVerificationRequest { + self.acceptKeyVerificationRequest(keyVerificationRequest) + } else { + self.downloadOtherDeviceKeys() + } + } + + private func acceptKeyVerificationRequest(_ keyVerificationRequest: MXKeyVerificationRequest) { + + self.update(viewState: .loading) + + keyVerificationRequest.accept(withMethods: self.keyVerificationService.supportedKeyVerificationMethods(), success: { [weak self] in + guard let self = self else { + return + } + + self.coordinatorDelegate?.keyVerificationDataLoadingViewModel(self, didAcceptKeyVerificationRequest: keyVerificationRequest) + + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + }) + } + + private func downloadOtherDeviceKeys() { + guard let crypto = session.crypto, + let otherUserId = self.otherUserId, + let otherDeviceId = self.otherDeviceId else { + self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) + NSLog("[KeyVerificationDataLoadingViewModel] Error session.crypto is nil") + return + } + + if let otherUser = session.user(withUserId: otherUserId) { + + self.update(viewState: .loading) + + self.currentOperation = crypto.downloadKeys([otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap, crossSigningKeysMap) in + guard let sself = self else { + return + } + + if let otherDevice = usersDevicesMap?.object(forDevice: otherDeviceId, forUser: otherUserId) { + sself.update(viewState: .loaded) + sself.coordinatorDelegate?.keyVerificationDataLoadingViewModel(sself, didLoadUser: otherUser, device: otherDevice) + } else { + sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) + } + + }, failure: { [weak self] (error) in + guard let sself = self else { + return + } + + let finalError = error ?? KeyVerificationDataLoadingViewModelError.unknown + + sself.update(viewState: .error(finalError)) + }) + + } else { + self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) + } + } + + private func update(viewState: KeyVerificationDataLoadingViewState) { + self.viewDelegate?.keyVerificationDataLoadingViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift new file mode 100644 index 000000000..f162f643f --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewModelType.swift @@ -0,0 +1,39 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationDataLoadingViewModelViewDelegate: class { + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didUpdateViewState viewSate: KeyVerificationDataLoadingViewState) +} + +protocol KeyVerificationDataLoadingViewModelCoordinatorDelegate: class { + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction) + func keyVerificationDataLoadingViewModel(_ viewModel: KeyVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) + func keyVerificationDataLoadingViewModelDidCancel(_ viewModel: KeyVerificationDataLoadingViewModelType) +} + +/// Protocol describing the view model used by `KeyVerificationDataLoadingViewController` +protocol KeyVerificationDataLoadingViewModelType { + + var viewDelegate: KeyVerificationDataLoadingViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationDataLoadingViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationDataLoadingViewAction) +} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewState.swift b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewState.swift similarity index 88% rename from Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewState.swift rename to Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewState.swift index 1f457efc6..c0968d7d5 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewState.swift +++ b/Riot/Modules/KeyVerification/Common/Loading/KeyVerificationDataLoadingViewState.swift @@ -18,8 +18,8 @@ import Foundation -/// DeviceVerificationDataLoadingViewController view state -enum DeviceVerificationDataLoadingViewState { +/// KeyVerificationDataLoadingViewController view state +enum KeyVerificationDataLoadingViewState { case loading case loaded case error(Error) diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinator.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinator.swift new file mode 100644 index 000000000..fab2d4da4 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinator.swift @@ -0,0 +1,76 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +enum KeyVerificationScanning { + case scannedOtherQRCode(MXQRCodeData) + case myQRCodeScanned +} + +final class KeyVerificationScanConfirmationCoordinator: KeyVerificationScanConfirmationCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var keyVerificationScanConfirmationViewModel: KeyVerificationScanConfirmationViewModelType + private let keyVerificationScanConfirmationViewController: KeyVerificationScanConfirmationViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationScanConfirmationCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, transaction: MXQRCodeTransaction, codeScanning: KeyVerificationScanning, verificationKind: KeyVerificationKind) { + self.session = session + + let keyVerificationScanConfirmationViewModel = KeyVerificationScanConfirmationViewModel(session: self.session, transaction: transaction, codeScanning: codeScanning, verificationKind: verificationKind) + let keyVerificationScanConfirmationViewController = KeyVerificationScanConfirmationViewController.instantiate(with: keyVerificationScanConfirmationViewModel) + self.keyVerificationScanConfirmationViewModel = keyVerificationScanConfirmationViewModel + self.keyVerificationScanConfirmationViewController = keyVerificationScanConfirmationViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationScanConfirmationViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationScanConfirmationViewController + } +} + +// MARK: - KeyVerificationScanConfirmationViewModelCoordinatorDelegate +extension KeyVerificationScanConfirmationCoordinator: KeyVerificationScanConfirmationViewModelCoordinatorDelegate { + + func keyVerificationScanConfirmationViewModelDidComplete(_ viewModel: KeyVerificationScanConfirmationViewModelType) { + self.delegate?.keyVerificationScanConfirmationCoordinatorDidComplete(self) + } + + func keyVerificationScanConfirmationViewModelDidCancel(_ viewModel: KeyVerificationScanConfirmationViewModelType) { + self.delegate?.keyVerificationScanConfirmationCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift new file mode 100644 index 000000000..35a2381c4 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationScanConfirmationCoordinatorDelegate: class { + func keyVerificationScanConfirmationCoordinatorDidComplete(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) + func keyVerificationScanConfirmationCoordinatorDidCancel(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) +} + +/// `KeyVerificationScanConfirmationCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationScanConfirmationCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationScanConfirmationCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewAction.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewAction.swift new file mode 100644 index 000000000..3a6fe0f08 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationScanConfirmationViewController view actions exposed to view model +enum KeyVerificationScanConfirmationViewAction { + case loadData + case acknowledgeOtherScannedMyCode(_ otherScannedMyCode: Bool) + case cancel +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.storyboard b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.storyboard new file mode 100644 index 000000000..8021438b7 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.storyboard @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.swift new file mode 100644 index 000000000..ec3133946 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.swift @@ -0,0 +1,241 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class KeyVerificationScanConfirmationViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let buttonBackgroundColorAlpha: CGFloat = 0.2 + static let buttonCornerRadius: CGFloat = 6.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var titleLabel: UILabel! + + @IBOutlet private weak var waitingLabel: UILabel! + + @IBOutlet private weak var scannedContentView: UIView! + @IBOutlet private weak var scannedInformationLabel: UILabel! + @IBOutlet private weak var rejectButton: UIButton! + @IBOutlet private weak var confirmButton: UIButton! + + // MARK: Private + + private var viewModel: KeyVerificationScanConfirmationViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyVerificationScanConfirmationViewModelType) -> KeyVerificationScanConfirmationViewController { + let viewController = StoryboardScene.KeyVerificationScanConfirmationViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if self.scannedContentView.isHidden == false { + self.confirmButton.layer.cornerRadius = Constants.buttonCornerRadius + self.rejectButton.layer.cornerRadius = Constants.buttonCornerRadius + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.titleLabel.textColor = theme.textPrimaryColor + self.waitingLabel.textColor = theme.textSecondaryColor + self.scannedInformationLabel.textColor = theme.textPrimaryColor + self.confirmButton.vc_setBackgroundColor(theme.tintColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + self.rejectButton.vc_setBackgroundColor(theme.noticeColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.title = VectorL10n.keyVerificationVerifyQrCodeTitle + + self.confirmButton.layer.masksToBounds = true + self.rejectButton.layer.masksToBounds = true + } + + private func render(viewState: KeyVerificationScanConfirmationViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(let viewData): + self.renderLoaded(viewData: viewData) + case .error(let error): + self.render(error: error) + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: KeyVerificationScanConfirmationViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.waitingLabel.isHidden = !viewData.isScanning + self.scannedContentView.isHidden = viewData.isScanning + + var title: String + var waitingInfo: String? + var scannedInfo: String? + + if viewData.isScanning { + title = VectorL10n.keyVerificationScanConfirmationScanningTitle + + switch viewData.verificationKind { + case .device: + waitingInfo = VectorL10n.keyVerificationScanConfirmationScanningDeviceWaitingOther + case .user: + waitingInfo = VectorL10n.keyVerificationScanConfirmationScanningUserWaitingOther(viewData.otherDisplayName) + } + } else { + title = VectorL10n.keyVerificationScanConfirmationScannedTitle + + switch viewData.verificationKind { + case .device: + scannedInfo = VectorL10n.keyVerificationScanConfirmationScannedDeviceInformation + case .user: + scannedInfo = VectorL10n.keyVerificationScanConfirmationScannedUserInformation(viewData.otherDisplayName) + } + } + + self.titleLabel.text = title + self.waitingLabel.text = waitingInfo + self.scannedInformationLabel.text = scannedInfo + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + + // MARK: - Actions + + @IBAction private func rejectButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(false)) + } + + @IBAction private func confirmButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(true)) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyVerificationScanConfirmationViewModelViewDelegate +extension KeyVerificationScanConfirmationViewController: KeyVerificationScanConfirmationViewModelViewDelegate { + + func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModel.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModel.swift new file mode 100644 index 000000000..7407a0e2f --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModel.swift @@ -0,0 +1,137 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyVerificationScanConfirmationViewModel: KeyVerificationScanConfirmationViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let transaction: MXQRCodeTransaction + private let codeScanning: KeyVerificationScanning + private let verificationKind: KeyVerificationKind + + private var isScanning: Bool { + if case .scannedOtherQRCode = self.codeScanning { + return true + } + return false + } + + // MARK: Public + + weak var viewDelegate: KeyVerificationScanConfirmationViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationScanConfirmationViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, + transaction: MXQRCodeTransaction, + codeScanning: KeyVerificationScanning, + verificationKind: KeyVerificationKind) { + self.session = session + self.transaction = transaction + self.codeScanning = codeScanning + self.verificationKind = verificationKind + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: KeyVerificationScanConfirmationViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .acknowledgeOtherScannedMyCode(let otherHasScannedMyCode): + self.transaction.otherUserScannedMyQrCode(otherHasScannedMyCode) + if otherHasScannedMyCode == false { self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidCancel(self) + } else { + self.update(viewState: .loading) + } + case .cancel: + self.cancel() + } + } + + // MARK: - Private + + private func loadData() { + let otherUserId = self.transaction.otherUserId + let otherUser = self.session.user(withUserId: otherUserId) + let otherDisplayName = otherUser?.displayname ?? otherUserId + + let viewData = KeyVerificationScanConfirmationViewData(isScanning: self.isScanning, verificationKind: self.verificationKind, otherDisplayName: otherDisplayName) + self.update(viewState: .loaded(viewData)) + + self.registerTransactionDidStateChangeNotification() + + if case .scannedOtherQRCode(let qrCodeData) = self.codeScanning { + self.transaction.userHasScannedOtherQrCodeData(qrCodeData) + } + } + + private func update(viewState: KeyVerificationScanConfirmationViewState) { + self.viewDelegate?.keyVerificationScanConfirmationViewModel(self, didUpdateViewState: viewState) + } + + private func cancel() { + self.transaction.cancel(with: MXTransactionCancelCode.user()) + self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidCancel(self) + } + + // MARK: - MXKeyVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: self.transaction) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXQRCodeTransaction else { + return + } + + switch transaction.state { + case .verified: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidComplete(self) + case .cancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case .cancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift new file mode 100644 index 000000000..03d843560 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationScanConfirmationViewModelViewDelegate: class { + func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState) +} + +protocol KeyVerificationScanConfirmationViewModelCoordinatorDelegate: class { + func keyVerificationScanConfirmationViewModelDidComplete(_ viewModel: KeyVerificationScanConfirmationViewModelType) + func keyVerificationScanConfirmationViewModelDidCancel(_ viewModel: KeyVerificationScanConfirmationViewModelType) +} + +/// Protocol describing the view model used by `KeyVerificationScanConfirmationViewController` +protocol KeyVerificationScanConfirmationViewModelType { + + var viewDelegate: KeyVerificationScanConfirmationViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationScanConfirmationViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationScanConfirmationViewAction) +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewState.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewState.swift new file mode 100644 index 000000000..c91b35a9f --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewState.swift @@ -0,0 +1,34 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationScanConfirmationViewData { + let isScanning: Bool + let verificationKind: KeyVerificationKind + let otherDisplayName: String +} + +/// KeyVerificationScanConfirmationViewController view state +enum KeyVerificationScanConfirmationViewState { + case loading + case loaded(_ viewData: KeyVerificationScanConfirmationViewData) + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} diff --git a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.storyboard b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard similarity index 98% rename from Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.storyboard rename to Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard index 33334ca01..43b7fe2ee 100644 --- a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard @@ -10,10 +10,10 @@ - + - + diff --git a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift similarity index 62% rename from Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift rename to Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift index b9ebde0b5..fa94e23b5 100644 --- a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift +++ b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift @@ -18,12 +18,12 @@ import UIKit -protocol DeviceVerificationVerifiedViewControllerDelegate: class { - func deviceVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: DeviceVerificationVerifiedViewController) - func deviceVerificationVerifiedViewControllerDidCancel(_ viewController: DeviceVerificationVerifiedViewController) +protocol KeyVerificationVerifiedViewControllerDelegate: class { + func keyVerificationVerifiedViewControllerDidTapSetupAction(_ viewController: KeyVerificationVerifiedViewController) + func keyVerificationVerifiedViewControllerDidCancel(_ viewController: KeyVerificationVerifiedViewController) } -final class DeviceVerificationVerifiedViewController: UIViewController { +final class KeyVerificationVerifiedViewController: UIViewController { // MARK: - Properties @@ -39,16 +39,18 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // MARK: Private private var theme: Theme! + private var verificationKind: KeyVerificationKind = .user // MARK: Public - weak var delegate: DeviceVerificationVerifiedViewControllerDelegate? + weak var delegate: KeyVerificationVerifiedViewControllerDelegate? // MARK: - Setup - class func instantiate() -> DeviceVerificationVerifiedViewController { - let viewController = StoryboardScene.DeviceVerificationVerifiedViewController.initialScene.instantiate() + class func instantiate(with verificationKind: KeyVerificationKind) -> KeyVerificationVerifiedViewController { + let viewController = StoryboardScene.KeyVerificationVerifiedViewController.initialScene.instantiate() viewController.theme = ThemeService.shared().theme + viewController.verificationKind = verificationKind return viewController } @@ -59,7 +61,6 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // Do any additional setup after loading the view. - self.title = VectorL10n.deviceVerificationTitle self.vc_removeBackTitle() self.setupViews() @@ -81,9 +82,28 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // MARK: - Private private func setupViews() { - self.titleLabel.text = VectorL10n.deviceVerificationVerifiedTitle - self.description1Label.text = VectorL10n.deviceVerificationVerifiedDescription1 - self.description2Label.text = VectorL10n.deviceVerificationVerifiedDescription2 + let title: String + let bodyTitle: String + let descriptionTextPart1: String + let descriptionTextPart2: String + + switch self.verificationKind { + case .device: + title = VectorL10n.deviceVerificationTitle + bodyTitle = VectorL10n.deviceVerificationVerifiedTitle + descriptionTextPart1 = VectorL10n.deviceVerificationVerifiedDescription1 + descriptionTextPart2 = VectorL10n.deviceVerificationVerifiedDescription2 + case .user: + title = VectorL10n.keyVerificationUserTitle + bodyTitle = VectorL10n.deviceVerificationVerifiedTitle + descriptionTextPart1 = VectorL10n.keyVerificationVerifiedUserDescription1 + descriptionTextPart2 = VectorL10n.keyVerificationVerifiedUserDescription2 + } + + self.title = title + self.titleLabel.text = bodyTitle + self.description1Label.text = descriptionTextPart1 + self.description2Label.text = descriptionTextPart2 self.okButton.setTitle(VectorL10n.deviceVerificationVerifiedGotItButton, for: .normal) } @@ -103,7 +123,7 @@ final class DeviceVerificationVerifiedViewController: UIViewController { self.okButtonBackgroundView.backgroundColor = theme.backgroundColor theme.applyStyle(onButton: self.okButton) - } + } private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) @@ -116,6 +136,6 @@ final class DeviceVerificationVerifiedViewController: UIViewController { } @IBAction private func validateButtonAction(_ sender: Any) { - self.delegate?.deviceVerificationVerifiedViewControllerDidTapSetupAction(self) + self.delegate?.keyVerificationVerifiedViewControllerDidTapSetupAction(self) } } diff --git a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinator.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinator.swift new file mode 100644 index 000000000..96b3e8a8e --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinator.swift @@ -0,0 +1,71 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyVerificationVerifyBySASCoordinator: KeyVerificationVerifyBySASCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var keyVerificationVerifyViewModel: KeyVerificationVerifyBySASViewModelType + private let keyVerificationVerifyViewController: KeyVerificationVerifyBySASViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationVerifyBySASCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) { + self.session = session + + let keyVerificationVerifyViewModel = KeyVerificationVerifyBySASViewModel(session: self.session, transaction: transaction, verificationKind: verificationKind) + let keyVerificationVerifyViewController = KeyVerificationVerifyBySASViewController.instantiate(with: keyVerificationVerifyViewModel) + self.keyVerificationVerifyViewModel = keyVerificationVerifyViewModel + self.keyVerificationVerifyViewController = keyVerificationVerifyViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationVerifyViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationVerifyViewController + } +} + +// MARK: - DeviceVerificationVerifyViewModelCoordinatorDelegate +extension KeyVerificationVerifyBySASCoordinator: KeyVerificationVerifyBySASViewModelCoordinatorDelegate { + + func keyVerificationVerifyViewModelDidComplete(_ viewModel: KeyVerificationVerifyBySASViewModelType) { + self.delegate?.keyVerificationVerifyBySASCoordinatorDidComplete(self) + } + + func keyVerificationVerifyViewModelDidCancel(_ viewModel: KeyVerificationVerifyBySASViewModelType) { + self.delegate?.keyVerificationVerifyBySASCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift similarity index 54% rename from Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinatorType.swift rename to Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift index d9816480b..378e2517e 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASCoordinatorType.swift @@ -18,12 +18,12 @@ import Foundation -protocol DeviceVerificationVerifyCoordinatorDelegate: class { - func deviceVerificationVerifyCoordinatorDidComplete(_ coordinator: DeviceVerificationVerifyCoordinatorType) - func deviceVerificationVerifyCoordinatorDidCancel(_ coordinator: DeviceVerificationVerifyCoordinatorType) +protocol KeyVerificationVerifyBySASCoordinatorDelegate: class { + func keyVerificationVerifyBySASCoordinatorDidComplete(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) + func keyVerificationVerifyBySASCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyBySASCoordinatorType) } -/// `DeviceVerificationVerifyCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. -protocol DeviceVerificationVerifyCoordinatorType: Coordinator, Presentable { - var delegate: DeviceVerificationVerifyCoordinatorDelegate? { get } +/// `KeyVerificationVerifyBySASCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationVerifyBySASCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationVerifyBySASCoordinatorDelegate? { get } } diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewAction.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewAction.swift similarity index 86% rename from Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewAction.swift rename to Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewAction.swift index dfc897fb1..6972d3e49 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewAction.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewAction.swift @@ -18,8 +18,8 @@ import Foundation -/// DeviceVerificationVerifyViewController view actions exposed to view model -enum DeviceVerificationVerifyViewAction { +/// KeyVerificationVerifyBySASViewController view actions exposed to view model +enum KeyVerificationVerifyBySASViewAction { case loadData case confirm case complete diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.storyboard b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard similarity index 97% rename from Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.storyboard rename to Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard index 9f488dae8..afd2968eb 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard @@ -10,10 +10,10 @@ - + - + @@ -86,12 +86,13 @@ - + + - + @@ -141,7 +142,6 @@ - @@ -153,11 +153,11 @@ - - + + diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.swift similarity index 79% rename from Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift rename to Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.swift index 7869f9a31..b61883181 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.swift @@ -18,7 +18,7 @@ import UIKit -final class DeviceVerificationVerifyViewController: UIViewController { +final class KeyVerificationVerifyBySASViewController: UIViewController { // MARK: - Constants @@ -38,15 +38,15 @@ final class DeviceVerificationVerifyViewController: UIViewController { // MARK: Private - private var viewModel: DeviceVerificationVerifyViewModelType! + private var viewModel: KeyVerificationVerifyBySASViewModelType! private var theme: Theme! private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! // MARK: - Setup - class func instantiate(with viewModel: DeviceVerificationVerifyViewModelType) -> DeviceVerificationVerifyViewController { - let viewController = StoryboardScene.DeviceVerificationVerifyViewController.initialScene.instantiate() + class func instantiate(with viewModel: KeyVerificationVerifyBySASViewModelType) -> KeyVerificationVerifyBySASViewController { + let viewController = StoryboardScene.KeyVerificationVerifyBySASViewController.initialScene.instantiate() viewController.viewModel = viewModel viewController.theme = ThemeService.shared().theme return viewController @@ -59,7 +59,6 @@ final class DeviceVerificationVerifyViewController: UIViewController { // Do any additional setup after loading the view. - self.title = VectorL10n.deviceVerificationTitle self.vc_removeBackTitle() self.setupViews() @@ -123,16 +122,33 @@ final class DeviceVerificationVerifyViewController: UIViewController { self.scrollView.keyboardDismissMode = .interactive - if viewModel.emojis != nil { + let isVerificationByEmoji = viewModel.emojis != nil + + if isVerificationByEmoji { self.decimalLabel.isHidden = true - self.titleLabel.text = VectorL10n.deviceVerificationVerifyTitleEmoji } else { self.emojisCollectionView.isHidden = true - self.titleLabel.text = VectorL10n.deviceVerificationVerifyTitleNumber self.decimalLabel.text = self.viewModel.decimal } + + let title: String + let instructionText: String + let adviceText: String + + switch viewModel.verificationKind { + case .device: + title = VectorL10n.deviceVerificationTitle + instructionText = isVerificationByEmoji ? VectorL10n.deviceVerificationVerifyTitleEmoji : VectorL10n.deviceVerificationVerifyTitleNumber + adviceText = VectorL10n.deviceVerificationSecurityAdvice + case .user: + title = VectorL10n.keyVerificationUserTitle + instructionText = isVerificationByEmoji ? VectorL10n.keyVerificationVerifyUserTitleEmoji : VectorL10n.keyVerificationVerifyUserTitleNumber + adviceText = VectorL10n.deviceVerificationSecurityAdvice + } - self.informationLabel.text = VectorL10n.deviceVerificationSecurityAdvice + self.title = title + self.titleLabel.text = instructionText + self.informationLabel.text = adviceText self.waitingPartnerLabel.text = VectorL10n.deviceVerificationVerifyWaitPartner self.waitingPartnerLabel.isHidden = true @@ -140,7 +156,7 @@ final class DeviceVerificationVerifyViewController: UIViewController { self.continueButton.setTitle(VectorL10n.continue, for: .normal) } - private func render(viewState: DeviceVerificationVerifyViewState) { + private func render(viewState: KeyVerificationVerifyViewState) { switch viewState { case .loading: self.renderLoading() @@ -205,15 +221,15 @@ final class DeviceVerificationVerifyViewController: UIViewController { // MARK: - DeviceVerificationVerifyViewModelViewDelegate -extension DeviceVerificationVerifyViewController: DeviceVerificationVerifyViewModelViewDelegate { +extension KeyVerificationVerifyBySASViewController: KeyVerificationVerifyBySASViewModelViewDelegate { - func deviceVerificationVerifyViewModel(_ viewModel: DeviceVerificationVerifyViewModelType, didUpdateViewState viewSate: DeviceVerificationVerifyViewState) { + func keyVerificationVerifyBySASViewModel(_ viewModel: KeyVerificationVerifyBySASViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyViewState) { self.render(viewState: viewSate) } } -extension DeviceVerificationVerifyViewController: UICollectionViewDataSource { +extension KeyVerificationVerifyBySASViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { guard let emojis = self.viewModel.emojis else { @@ -223,10 +239,8 @@ extension DeviceVerificationVerifyViewController: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VerifyEmojiCollectionViewCell", for: indexPath) as? VerifyEmojiCollectionViewCell else { - return UICollectionViewCell() - } + + let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: VerifyEmojiCollectionViewCell.self) guard let emoji = self.viewModel.emojis?[indexPath.row] else { return UICollectionViewCell() diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModel.swift similarity index 73% rename from Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift rename to Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModel.swift index 2be7640a9..3f066437e 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModel.swift @@ -18,7 +18,7 @@ import Foundation -final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModelType { +final class KeyVerificationVerifyBySASViewModel: KeyVerificationVerifyBySASViewModelType { // MARK: - Properties @@ -29,18 +29,21 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel // MARK: Public - weak var viewDelegate: DeviceVerificationVerifyViewModelViewDelegate? - weak var coordinatorDelegate: DeviceVerificationVerifyViewModelCoordinatorDelegate? - var emojis: [MXEmojiRepresentation]? - var decimal: String? + weak var viewDelegate: KeyVerificationVerifyBySASViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationVerifyBySASViewModelCoordinatorDelegate? + + let emojis: [MXEmojiRepresentation]? + let decimal: String? + let verificationKind: KeyVerificationKind // MARK: - Setup - init(session: MXSession, transaction: MXSASTransaction) { + init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) { self.session = session self.transaction = transaction self.emojis = self.transaction.sasEmoji self.decimal = self.transaction.sasDecimal + self.verificationKind = verificationKind } deinit { @@ -48,17 +51,17 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel // MARK: - Public - func process(viewAction: DeviceVerificationVerifyViewAction) { + func process(viewAction: KeyVerificationVerifyBySASViewAction) { switch viewAction { case .loadData: self.registerTransactionDidStateChangeNotification(transaction: transaction) case .confirm: self.confirmTransaction() case .complete: - self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidComplete(self) + self.coordinatorDelegate?.keyVerificationVerifyViewModelDidComplete(self) case .cancel: self.cancelTransaction() - self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidCancel(self) + self.coordinatorDelegate?.keyVerificationVerifyViewModelDidCancel(self) } } @@ -74,18 +77,18 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel self.transaction.cancel(with: MXTransactionCancelCode.user()) } - private func update(viewState: DeviceVerificationVerifyViewState) { - self.viewDelegate?.deviceVerificationVerifyViewModel(self, didUpdateViewState: viewState) + private func update(viewState: KeyVerificationVerifyViewState) { + self.viewDelegate?.keyVerificationVerifyBySASViewModel(self, didUpdateViewState: viewState) } - // MARK: - MXDeviceVerificationTransactionDidChange + // MARK: - MXKeyVerificationTransactionDidChange private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) { - NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction) + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) } private func unregisterTransactionDidStateChangeNotification() { - NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil) + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) } @objc private func transactionDidStateChange(notification: Notification) { @@ -97,7 +100,7 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel case MXSASTransactionStateVerified: self.unregisterTransactionDidStateChangeNotification() self.update(viewState: .loaded) - self.coordinatorDelegate?.deviceVerificationVerifyViewModelDidComplete(self) + self.coordinatorDelegate?.keyVerificationVerifyViewModelDidComplete(self) case MXSASTransactionStateCancelled: guard let reason = transaction.reasonCancelCode else { return diff --git a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift new file mode 100644 index 000000000..a5686bc7a --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewModelType.swift @@ -0,0 +1,41 @@ +// File created from ScreenTemplate +// $ createScreen.sh DeviceVerification/Verify DeviceVerificationVerify +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationVerifyBySASViewModelViewDelegate: class { + func keyVerificationVerifyBySASViewModel(_ viewModel: KeyVerificationVerifyBySASViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyViewState) +} + +protocol KeyVerificationVerifyBySASViewModelCoordinatorDelegate: class { + func keyVerificationVerifyViewModelDidComplete(_ viewModel: KeyVerificationVerifyBySASViewModelType) + func keyVerificationVerifyViewModelDidCancel(_ viewModel: KeyVerificationVerifyBySASViewModelType) +} + +/// Protocol describing the view model used by `KeyVerificationVerifyBySASViewController` +protocol KeyVerificationVerifyBySASViewModelType { + + var viewDelegate: KeyVerificationVerifyBySASViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationVerifyBySASViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationVerifyBySASViewAction) + + var emojis: [MXEmojiRepresentation]? { get } + var decimal: String? { get } + var verificationKind: KeyVerificationKind { get } +} diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewState.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewState.swift similarity index 90% rename from Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewState.swift rename to Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewState.swift index 312db4e64..e01f1c743 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewState.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewState.swift @@ -18,8 +18,8 @@ import Foundation -/// DeviceVerificationVerifyViewController view state -enum DeviceVerificationVerifyViewState { +/// KeyVerificationVerifyBySASViewController view state +enum KeyVerificationVerifyViewState { case loading case loaded // verified case cancelled(MXTransactionCancelCode) diff --git a/Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift b/Riot/Modules/KeyVerification/Common/Verify/SAS/Views/VerifyEmojiCollectionViewCell.swift similarity index 88% rename from Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift rename to Riot/Modules/KeyVerification/Common/Verify/SAS/Views/VerifyEmojiCollectionViewCell.swift index 7d1780062..e609f6b38 100644 --- a/Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/Views/VerifyEmojiCollectionViewCell.swift @@ -15,8 +15,9 @@ */ import UIKit +import Reusable -class VerifyEmojiCollectionViewCell: UICollectionViewCell, Themable { +class VerifyEmojiCollectionViewCell: UICollectionViewCell, Reusable, Themable { @IBOutlet weak var emoji: UILabel! @IBOutlet weak var name: UILabel! diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift new file mode 100644 index 000000000..1002d8cd3 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift @@ -0,0 +1,86 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationRequest: MXKeyVerificationRequest + + private var keyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType + private let keyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.keyVerificationRequest = keyVerificationRequest + + let keyVerificationVerifyByScanningViewModel = KeyVerificationVerifyByScanningViewModel(session: self.session, keyVerificationRequest: keyVerificationRequest) + let keyVerificationVerifyByScanningViewController = KeyVerificationVerifyByScanningViewController.instantiate(with: keyVerificationVerifyByScanningViewModel) + self.keyVerificationVerifyByScanningViewModel = keyVerificationVerifyByScanningViewModel + self.keyVerificationVerifyByScanningViewController = keyVerificationVerifyByScanningViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationVerifyByScanningViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationVerifyByScanningViewController + } +} + +// MARK: - KeyVerificationVerifyByScanningViewModelCoordinatorDelegate +extension KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate { + + func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCancel(self) + } + + func keyVerificationVerifyByScanningViewModelCannotScan(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorCannotScan(self) + } + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didCompleteWithSASTransaction: transaction) + } + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didScanOtherQRCodeData: qrCodeData, withTransaction: transaction) + } + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, qrCodeDidScannedByOtherWithTransaction: transaction) + } +} diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift new file mode 100644 index 000000000..faa3ec9ee --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift @@ -0,0 +1,32 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationVerifyByScanningCoordinatorDelegate: class { + func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) +} + +/// `KeyVerificationVerifyByScanningCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationVerifyByScanningCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift new file mode 100644 index 000000000..617dd202c --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationVerifyByScanningViewController view actions exposed to view model +enum KeyVerificationVerifyByScanningViewAction { + case loadData + case cancel + case scannedCode(payloadData: Data) + case cannotScan + case acknowledgeOtherScannedMyCode(Bool) + case acknowledgeMyUserScannedOtherCode +} diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard new file mode 100644 index 000000000..7aa50203c --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift new file mode 100644 index 000000000..43b575859 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift @@ -0,0 +1,321 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class KeyVerificationVerifyByScanningViewController: UIViewController { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var closeButton: UIButton! + + @IBOutlet private weak var titleView: UIView! + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var codeImageView: UIImageView! + + @IBOutlet private weak var scanCodeButton: UIButton! + @IBOutlet private weak var cannotScanButton: UIButton! + + @IBOutlet weak var qrCodeContainerView: UIView! + + @IBOutlet weak var scanButtonContainerView: UIView! + + // MARK: Private + + private var viewModel: KeyVerificationVerifyByScanningViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + private var cameraAccessAlertPresenter: CameraAccessAlertPresenter! + private var cameraAccessManager: CameraAccessManager! + + private weak var qrCodeReaderViewController: QRCodeReaderViewController! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyVerificationVerifyByScanningViewModelType) -> KeyVerificationVerifyByScanningViewController { + let viewController = StoryboardScene.KeyVerificationVerifyByScanningViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + self.cameraAccessAlertPresenter = CameraAccessAlertPresenter() + self.cameraAccessManager = CameraAccessManager() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.titleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textPrimaryColor + + if let themableCloseButton = self.closeButton as? Themable { + themableCloseButton.update(theme: theme) + } + + theme.applyStyle(onButton: self.scanCodeButton) + theme.applyStyle(onButton: self.cannotScanButton) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.titleView.isHidden = self.navigationController != nil + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.title = VectorL10n.keyVerificationVerifyQrCodeTitle + self.titleLabel.text = VectorL10n.keyVerificationVerifyQrCodeTitle + self.informationLabel.text = VectorL10n.keyVerificationVerifyQrCodeInformation + + self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal) + self.cannotScanButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeCannotScanAction, for: .normal) + } + + private func render(viewState: KeyVerificationVerifyByScanningViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(viewData: let viewData): + self.renderLoaded(viewData: viewData) + case .error(let error): + self.render(error: error) + case .scannedCodeValidated(let isValid): + self.renderScannedCode(valid: isValid) + case .otherUserScannedMyCode: + self.renderOtherScannedMyCode() + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: KeyVerificationVerifyByScanningViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + let hideQRCodeImage: Bool + + if let qrCodePayloadData = viewData.qrCodeData { + hideQRCodeImage = false + self.codeImageView.image = self.qrCodeImage(from: qrCodePayloadData) + } else { + hideQRCodeImage = true + } + + self.qrCodeContainerView.isHidden = hideQRCodeImage + self.scanButtonContainerView.isHidden = !viewData.showScanAction + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func qrCodeImage(from data: Data) -> UIImage? { + let codeGenerator = QRCodeGenerator() + return codeGenerator.generateCode(from: data, with: self.codeImageView.frame.size) + } + + private func presentQRCodeReader(animated: Bool) { + let qrCodeViewController = QRCodeReaderViewController.instantiate() + qrCodeViewController.delegate = self + self.present(qrCodeViewController, animated: animated, completion: nil) + self.qrCodeReaderViewController = qrCodeViewController + } + + private func renderScannedCode(valid: Bool) { + if valid { + self.qrCodeReaderViewController.view.isUserInteractionEnabled = false + self.qrCodeReaderViewController.stopScanning() + self.presentCodeValidated(animated: true) { + self.dismiss(animated: true, completion: { + self.viewModel.process(viewAction: .acknowledgeMyUserScannedOtherCode) + }) + } + } + } + + private func renderOtherScannedMyCode() { + let alert = UIAlertController(title: VectorL10n.keyVerificationVerifyQrCodeOtherScanMyCodeTitle, + message: nil, + preferredStyle: .alert) + + let okAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "yes"), style: .default, handler: { _ in + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(true)) + }) + alert.addAction(okAction) + + let cancelAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "no"), style: .cancel, handler: { _ in + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(false)) + }) + alert.addAction(cancelAction) + + self.present(alert, animated: true, completion: nil) + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + private func presentCodeValidated(animated: Bool, completion: @escaping (() -> Void)) { + + let alert = UIAlertController(title: VectorL10n.keyVerificationVerifyQrCodeScanOtherCodeSuccessTitle, + message: VectorL10n.keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage, + preferredStyle: .alert) + + let okAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "ok"), style: .default, handler: { _ in + completion() + }) + alert.addAction(okAction) + + if let qrCodeReaderViewController = self.qrCodeReaderViewController { + qrCodeReaderViewController.present(alert, animated: animated, completion: nil) + } + } + + private func checkCameraAccessAndPresentQRCodeReader(animated: Bool) { + guard self.cameraAccessManager.isCameraAvailable else { + self.cameraAccessAlertPresenter.presentCameraUnavailableAlert(from: self, animated: animated) + return + } + + self.cameraAccessManager.askAndRequestCameraAccessIfNeeded { (granted) in + if granted { + self.presentQRCodeReader(animated: animated) + } else { + self.cameraAccessAlertPresenter.presentPermissionDeniedAlert(from: self, animated: animated) + } + } + } + + // MARK: - Actions + + @IBAction private func scanButtonAction(_ sender: Any) { + self.checkCameraAccessAndPresentQRCodeReader(animated: true) + } + + @IBAction private func cannotScanAction(_ sender: Any) { + self.viewModel.process(viewAction: .cannotScan) + } + + @IBAction private func closeButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .cancel) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyVerificationVerifyByScanningViewModelViewDelegate +extension KeyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewModelViewDelegate { + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) { + self.render(viewState: viewSate) + } +} + +// MARK: - QRCodeReaderViewControllerDelegate +extension KeyVerificationVerifyByScanningViewController: QRCodeReaderViewControllerDelegate { + + func qrCodeReaderViewController(_ viewController: QRCodeReaderViewController, didFound payloadData: Data) { + self.viewModel.process(viewAction: .scannedCode(payloadData: payloadData)) + } + + func qrCodeReaderViewControllerDidCancel(_ viewController: QRCodeReaderViewController) { + self.dismiss(animated: true, completion: nil) + } +} diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift new file mode 100644 index 000000000..8462e051b --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -0,0 +1,270 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum KeyVerificationVerifyByScanningViewModelError: Error { + case unknown +} + +final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationRequest: MXKeyVerificationRequest + private let qrCodeDataCoder: MXQRCodeDataCoder + private let keyVerificationManager: MXKeyVerificationManager + + private var qrCodeTransaction: MXQRCodeTransaction? + private var scannedQRCodeData: MXQRCodeData? + + // MARK: Public + + weak var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.keyVerificationManager = self.session.crypto.keyVerificationManager + self.keyVerificationRequest = keyVerificationRequest + self.qrCodeDataCoder = MXQRCodeDataCoder() + } + + deinit { + self.removePendingQRCodeTransaction() + } + + // MARK: - Public + + func process(viewAction: KeyVerificationVerifyByScanningViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .scannedCode(payloadData: let payloadData): + self.scannedQRCode(payloadData: payloadData) + case .cannotScan: + self.startSASVerification() + case .acknowledgeOtherScannedMyCode(let acknowledgeOtherScannedMyCode): + self.acknowledgeOtherScannedMyCode(acknowledgeOtherScannedMyCode) + case .cancel: + self.cancel() + case .acknowledgeMyUserScannedOtherCode: + self.acknowledgeScanOtherCode() + } + } + + // MARK: - Private + + private func loadData() { + + let qrCodePlayloadData: Data? + let canShowScanAction: Bool + + self.qrCodeTransaction = self.keyVerificationManager.qrCodeTransaction(withTransactionId: self.keyVerificationRequest.requestId) + + if let supportedVerificationMethods = self.keyVerificationRequest.myMethods { + + if let qrCodeData = self.qrCodeTransaction?.qrCodeData { + qrCodePlayloadData = self.qrCodeDataCoder.encode(qrCodeData) + } else { + qrCodePlayloadData = nil + } + + canShowScanAction = self.canShowScanAction(from: supportedVerificationMethods) + } else { + qrCodePlayloadData = nil + canShowScanAction = false + } + + let viewData = KeyVerificationVerifyByScanningViewData(qrCodeData: qrCodePlayloadData, + showScanAction: canShowScanAction) + + self.update(viewState: .loaded(viewData: viewData)) + + self.registerTransactionDidStateChangeNotification() + } + + private func canShowScanAction(from verificationMethods: [String]) -> Bool { + return verificationMethods.contains(MXKeyVerificationMethodQRCodeScan) + } + + private func cancel() { + self.cancelQRCodeTransaction() + self.keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) + } + + private func cancelQRCodeTransaction() { + guard let transaction = self.qrCodeTransaction else { + return + } + + transaction.cancel(with: MXTransactionCancelCode.user()) + } + + private func update(viewState: KeyVerificationVerifyByScanningViewState) { + self.viewDelegate?.keyVerificationVerifyByScanningViewModel(self, didUpdateViewState: viewState) + } + + // MARK: QR code + + private func scannedQRCode(payloadData: Data) { + self.scannedQRCodeData = self.qrCodeDataCoder.decode(payloadData) + + let isQRCodeValid = self.scannedQRCodeData != nil + + self.update(viewState: .scannedCodeValidated(isValid: isQRCodeValid)) + } + + private func acknowledgeScanOtherCode() { + guard let scannedQRCodeData = self.scannedQRCodeData else { + return + } + + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + + self.update(viewState: .loading) + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didScanOtherQRCodeData: scannedQRCodeData, withTransaction: qrCodeTransaction) + } + + private func acknowledgeOtherScannedMyCode(_ acknowledgeOtherScannedMyCode: Bool) { + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + + self.update(viewState: .loading) + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, qrCodeDidScannedByOtherWithTransaction: qrCodeTransaction) + } + + private func removePendingQRCodeTransaction() { + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + self.keyVerificationManager.removeQRCodeTransaction(withTransactionId: qrCodeTransaction.transactionId) + } + + // MARK: SAS + + private func startSASVerification() { + + self.update(viewState: .loading) + + self.session.crypto.keyVerificationManager.beginKeyVerification(from: self.keyVerificationRequest, method: MXKeyVerificationMethodSAS, success: { [weak self] (keyVerificationTransaction) in + guard let self = self else { + return + } + + // Remove pending QR code transaction, as we are going to use SAS verification + self.removePendingQRCodeTransaction() + + if keyVerificationTransaction is MXOutgoingSASTransaction == false { + NSLog("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing") + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown)) + } + + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + } + ) + } + + // MARK: - MXKeyVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXKeyVerificationTransaction else { + return + } + + guard self.keyVerificationRequest.requestId == transaction.transactionId else { + NSLog("[KeyVerificationVerifyByScanningViewModel] transactionDidStateChange: Not for our transaction (\(self.keyVerificationRequest.requestId)): \(transaction.transactionId)") + return + } + + if let sasTransaction = transaction as? MXSASTransaction { + self.sasTransactionDidStateChange(sasTransaction) + } else if let qrCodeTransaction = transaction as? MXQRCodeTransaction { + self.qrCodeTransactionDidStateChange(qrCodeTransaction) + } + } + + private func sasTransactionDidStateChange(_ transaction: MXSASTransaction) { + switch transaction.state { + case MXSASTransactionStateShowSAS: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didStartSASVerificationWithTransaction: transaction) + case MXSASTransactionStateCancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXSASTransactionStateCancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } + + private func qrCodeTransactionDidStateChange(_ transaction: MXQRCodeTransaction) { + switch transaction.state { + case .verified: + // Should not happen + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) + case .qrScannedByOther: + self.update(viewState: .otherUserScannedMyCode) + case .cancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case .cancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } +} diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift new file mode 100644 index 000000000..4be0568d5 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift @@ -0,0 +1,42 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationVerifyByScanningViewModelViewDelegate: class { + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) +} + +protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: class { + func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) +} + +/// Protocol describing the view model used by `KeyVerificationVerifyByScanningViewController` +protocol KeyVerificationVerifyByScanningViewModelType { + + var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationVerifyByScanningViewAction) +} diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift new file mode 100644 index 000000000..df48f6e91 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift @@ -0,0 +1,35 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationVerifyByScanningViewData { + let qrCodeData: Data? + let showScanAction: Bool +} + +/// KeyVerificationVerifyByScanningViewController view state +enum KeyVerificationVerifyByScanningViewState { + case loading + case loaded(viewData: KeyVerificationVerifyByScanningViewData) + case scannedCodeValidated(isValid: Bool) + case otherUserScannedMyCode + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingCoordinator.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinator.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingCoordinator.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinator.swift diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinatorType.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingCoordinatorType.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinatorType.swift diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewAction.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewAction.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewAction.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewAction.swift diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.storyboard b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewController.storyboard similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.storyboard rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewController.storyboard diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewController.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewController.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewController.swift diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewModel.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift similarity index 95% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewModel.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift index 6e62868c3..3652fd29b 100644 --- a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewModel.swift +++ b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift @@ -84,14 +84,14 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM self.viewDelegate?.deviceVerificationIncomingViewModel(self, didUpdateViewState: viewState) } - // MARK: - MXDeviceVerificationTransactionDidChange + // MARK: - MXKeyVerificationTransactionDidChange private func registerTransactionDidStateChangeNotification(transaction: MXIncomingSASTransaction) { - NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction) + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) } private func unregisterTransactionDidStateChangeNotification() { - NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil) + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) } @objc private func transactionDidStateChange(notification: Notification) { diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewModelType.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModelType.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewModelType.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModelType.swift diff --git a/Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewState.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewState.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Incoming/DeviceVerificationIncomingViewState.swift rename to Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewState.swift diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinator.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinator.swift new file mode 100644 index 000000000..7bbba325e --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinator.swift @@ -0,0 +1,71 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyVerificationSelfVerifyStartCoordinator: KeyVerificationSelfVerifyStartCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var keyVerificationSelfVerifyStartViewModel: KeyVerificationSelfVerifyStartViewModelType + private let keyVerificationSelfVerifyStartViewController: KeyVerificationSelfVerifyStartViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationSelfVerifyStartCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, otherDeviceId: String) { + self.session = session + + let keyVerificationSelfVerifyStartViewModel = KeyVerificationSelfVerifyStartViewModel(session: self.session, otherDeviceId: otherDeviceId) + let keyVerificationSelfVerifyStartViewController = KeyVerificationSelfVerifyStartViewController.instantiate(with: keyVerificationSelfVerifyStartViewModel) + self.keyVerificationSelfVerifyStartViewModel = keyVerificationSelfVerifyStartViewModel + self.keyVerificationSelfVerifyStartViewController = keyVerificationSelfVerifyStartViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationSelfVerifyStartViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationSelfVerifyStartViewController + } +} + +// MARK: - KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate +extension KeyVerificationSelfVerifyStartCoordinator: KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate { + + func keyVerificationSelfVerifyStartViewModel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) { + self.delegate?.keyVerificationSelfVerifyStartCoordinator(self, otherDidAcceptRequest: request) + } + + func keyVerificationSelfVerifyStartViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType) { + self.delegate?.keyVerificationSelfVerifyStartCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift new file mode 100644 index 000000000..faa2a8555 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartCoordinatorType.swift @@ -0,0 +1,31 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationSelfVerifyStartCoordinatorDelegate: class { + + func keyVerificationSelfVerifyStartCoordinator(_ coordinator: KeyVerificationSelfVerifyStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) + + func keyVerificationSelfVerifyStartCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyStartCoordinatorType) +} + +/// `KeyVerificationSelfVerifyStartCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationSelfVerifyStartCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationSelfVerifyStartCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewAction.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewAction.swift new file mode 100644 index 000000000..c155e60e1 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationSelfVerifyStartViewController view actions exposed to view model +enum KeyVerificationSelfVerifyStartViewAction { + case loadData + case startVerification + case cancel +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewController.storyboard b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewController.storyboard new file mode 100644 index 000000000..d11d8898a --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewController.storyboard @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewController.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewController.swift new file mode 100644 index 000000000..e209f5772 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewController.swift @@ -0,0 +1,200 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class KeyVerificationSelfVerifyStartViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let verifyButtonCornerRadius: CGFloat = 8.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var startVerificationButton: UIButton! + @IBOutlet private weak var verificationWaitingLabel: UILabel! + + @IBOutlet private weak var additionalInformationLabel: UILabel! + + // MARK: Private + + private var viewModel: KeyVerificationSelfVerifyStartViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyVerificationSelfVerifyStartViewModelType) -> KeyVerificationSelfVerifyStartViewController { + let viewController = StoryboardScene.KeyVerificationSelfVerifyStartViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.deviceVerificationTitle + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.startVerificationButton.layer.cornerRadius = Constants.verifyButtonCornerRadius + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.informationLabel.textColor = theme.textPrimaryColor + self.startVerificationButton.vc_setBackgroundColor(theme.tintColor, for: .normal) + self.verificationWaitingLabel.textColor = theme.textSecondaryColor + self.additionalInformationLabel.textColor = theme.textSecondaryColor + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.startVerificationButton.layer.masksToBounds = true + self.startVerificationButton.setTitle(VectorL10n.deviceVerificationSelfVerifyStartVerifyAction, for: .normal) + self.verificationWaitingLabel.text = VectorL10n.deviceVerificationSelfVerifyStartWaiting + self.informationLabel.text = VectorL10n.deviceVerificationSelfVerifyStartInformation + self.additionalInformationLabel.text = nil + } + + private func render(viewState: KeyVerificationSelfVerifyStartViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded: + self.renderLoaded() + case .error(let error): + self.render(error: error) + case .verificationPending: + self.renderVerificationPending() + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + + private func renderVerificationPending() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.startVerificationButton.isHidden = true + self.verificationWaitingLabel.isHidden = false + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + // MARK: - Actions + + @IBAction private func startVerificationButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .startVerification) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyVerificationSelfVerifyStartViewModelViewDelegate +extension KeyVerificationSelfVerifyStartViewController: KeyVerificationSelfVerifyStartViewModelViewDelegate { + + func keyVerificationSelfVerifyStartViewModel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType, didUpdateViewState viewSate: KeyVerificationSelfVerifyStartViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModel.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModel.swift new file mode 100644 index 000000000..b4f3e63ac --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModel.swift @@ -0,0 +1,144 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyVerificationSelfVerifyStartViewModel: KeyVerificationSelfVerifyStartViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let otherDeviceId: String + private let verificationManager: MXKeyVerificationManager + private let keyVerificationService: KeyVerificationService + + private var keyVerificationRequest: MXKeyVerificationRequest? + + // MARK: Public + + weak var viewDelegate: KeyVerificationSelfVerifyStartViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, otherDeviceId: String) { + self.session = session + self.verificationManager = session.crypto.keyVerificationManager + self.otherDeviceId = otherDeviceId + self.keyVerificationService = KeyVerificationService() + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: KeyVerificationSelfVerifyStartViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .startVerification: + self.startVerification() + case .cancel: + self.cancelKeyVerificationRequest() + self.coordinatorDelegate?.keyVerificationSelfVerifyStartViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + self.startVerification() + } + + private func startVerification() { + self.update(viewState: .verificationPending) + + self.verificationManager.requestVerificationByToDevice(withUserId: session.myUser.userId, deviceIds: [otherDeviceId], methods: self.keyVerificationService.supportedKeyVerificationMethods(), success: { [weak self] (keyVerificationRequest) in + guard let self = self else { + return + } + + self.keyVerificationRequest = keyVerificationRequest + self.update(viewState: .loaded) + self.registerKeyVerificationRequestDidChangeNotification(for: keyVerificationRequest) + }, failure: { [weak self] error in + self?.update(viewState: .error(error)) + }) + } + + private func update(viewState: KeyVerificationSelfVerifyStartViewState) { + self.viewDelegate?.keyVerificationSelfVerifyStartViewModel(self, didUpdateViewState: viewState) + } + + private func cancelKeyVerificationRequest() { + guard let keyVerificationRequest = self.keyVerificationRequest else { + return + } + + keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) + } + + // MARK: - MXKeyVerificationRequestDidChange + + private func registerKeyVerificationRequestDidChangeNotification(for keyVerificationRequest: MXKeyVerificationRequest) { + NotificationCenter.default.addObserver(self, selector: #selector(keyVerificationRequestDidChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: keyVerificationRequest) + } + + private func unregisterKeyVerificationRequestDidChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil) + } + + @objc private func keyVerificationRequestDidChange(notification: Notification) { + guard let keyVerificationRequest = notification.object as? MXKeyVerificationRequest else { + return + } + + guard let currentKeyVerificationRequest = self.keyVerificationRequest, keyVerificationRequest.requestId == currentKeyVerificationRequest.requestId else { + return + } + + switch keyVerificationRequest.state { + case MXKeyVerificationRequestStateAccepted: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.coordinatorDelegate?.keyVerificationSelfVerifyStartViewModel(self, otherDidAcceptRequest: currentKeyVerificationRequest) + case MXKeyVerificationRequestStateReady: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.coordinatorDelegate?.keyVerificationSelfVerifyStartViewModel(self, otherDidAcceptRequest: currentKeyVerificationRequest) + case MXKeyVerificationRequestStateCancelled: + guard let reason = keyVerificationRequest.reasonCancelCode else { + return + } + self.unregisterKeyVerificationRequestDidChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXKeyVerificationRequestStateCancelledByMe: + guard let reason = keyVerificationRequest.reasonCancelCode else { + return + } + self.unregisterKeyVerificationRequestDidChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + case MXKeyVerificationRequestStateExpired: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.update(viewState: .error(UserVerificationStartViewModelError.keyVerificationRequestExpired)) + default: + break + } + } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift new file mode 100644 index 000000000..0de1eccf5 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationSelfVerifyStartViewModelViewDelegate: class { + func keyVerificationSelfVerifyStartViewModel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType, didUpdateViewState viewSate: KeyVerificationSelfVerifyStartViewState) +} + +protocol KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate: class { + func keyVerificationSelfVerifyStartViewModel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) + func keyVerificationSelfVerifyStartViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyStartViewModelType) +} + +/// Protocol describing the view model used by `KeyVerificationSelfVerifyStartViewController` +protocol KeyVerificationSelfVerifyStartViewModelType { + + var viewDelegate: KeyVerificationSelfVerifyStartViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationSelfVerifyStartViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationSelfVerifyStartViewAction) +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewState.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewState.swift new file mode 100644 index 000000000..6b7abcf91 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyStart/KeyVerificationSelfVerifyStartViewState.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationSelfVerifyStartViewController view state +enum KeyVerificationSelfVerifyStartViewState { + case loading + case loaded + case verificationPending + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift new file mode 100644 index 000000000..522c6a7b3 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift @@ -0,0 +1,70 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyWaitCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var keyVerificationSelfVerifyWaitViewModel: KeyVerificationSelfVerifyWaitViewModelType + private let keyVerificationSelfVerifyWaitViewController: KeyVerificationSelfVerifyWaitViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationSelfVerifyWaitCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession) { + self.session = session + + let keyVerificationSelfVerifyWaitViewModel = KeyVerificationSelfVerifyWaitViewModel(session: self.session) + let keyVerificationSelfVerifyWaitViewController = KeyVerificationSelfVerifyWaitViewController.instantiate(with: keyVerificationSelfVerifyWaitViewModel) + self.keyVerificationSelfVerifyWaitViewModel = keyVerificationSelfVerifyWaitViewModel + self.keyVerificationSelfVerifyWaitViewController = keyVerificationSelfVerifyWaitViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationSelfVerifyWaitViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationSelfVerifyWaitViewController + } +} + +// MARK: - KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate +extension KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate { + func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) { + self.delegate?.keyVerificationSelfVerifyWaitCoordinator(self, didAcceptKeyVerificationRequest: keyVerificationRequest) + } + + func keyVerificationSelfVerifyWaitViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) { + self.delegate?.keyVerificationSelfVerifyWaitCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift new file mode 100644 index 000000000..d4b464d0e --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationSelfVerifyWaitCoordinatorDelegate: class { + func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) + func keyVerificationSelfVerifyWaitCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) +} + +/// `KeyVerificationSelfVerifyWaitCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationSelfVerifyWaitCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationSelfVerifyWaitCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift new file mode 100644 index 000000000..d7144e5e2 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewAction.swift @@ -0,0 +1,25 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationSelfVerifyWaitViewController view actions exposed to view model +enum KeyVerificationSelfVerifyWaitViewAction { + case loadData + case cancel +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard new file mode 100644 index 000000000..b60b96d58 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift similarity index 66% rename from Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift rename to Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift index 80ae30af7..d5045e360 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift @@ -1,7 +1,7 @@ // File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait /* - Copyright 2019 New Vector Ltd + Copyright 2020 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,23 +18,28 @@ import UIKit -final class DeviceVerificationDataLoadingViewController: UIViewController { +final class KeyVerificationSelfVerifyWaitViewController: UIViewController { + + // MARK: - Constants // MARK: - Properties // MARK: Outlets + @IBOutlet private weak var informationLabel: UILabel! + @IBOutlet private weak var verificationWaitingLabel: UILabel! + // MARK: Private - private var viewModel: DeviceVerificationDataLoadingViewModelType! + private var viewModel: KeyVerificationSelfVerifyWaitViewModelType! private var theme: Theme! private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! // MARK: - Setup - class func instantiate(with viewModel: DeviceVerificationDataLoadingViewModelType) -> DeviceVerificationDataLoadingViewController { - let viewController = StoryboardScene.DeviceVerificationDataLoadingViewController.initialScene.instantiate() + class func instantiate(with viewModel: KeyVerificationSelfVerifyWaitViewModelType) -> KeyVerificationSelfVerifyWaitViewController { + let viewController = StoryboardScene.KeyVerificationSelfVerifyWaitViewController.initialScene.instantiate() viewController.viewModel = viewModel viewController.theme = ThemeService.shared().theme return viewController @@ -47,21 +52,18 @@ final class DeviceVerificationDataLoadingViewController: UIViewController { // Do any additional setup after loading the view. - self.title = VectorL10n.deviceVerificationTitle - self.vc_removeBackTitle() - self.setupViews() - self.activityPresenter = ActivityIndicatorPresenter() self.errorPresenter = MXKErrorAlertPresentation() - + self.registerThemeServiceDidChangeThemeNotification() self.update(theme: self.theme) self.viewModel.viewDelegate = self + self.viewModel.process(viewAction: .loadData) } - + override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle } @@ -76,21 +78,33 @@ final class DeviceVerificationDataLoadingViewController: UIViewController { if let navigationBar = self.navigationController?.navigationBar { theme.applyStyle(onNavigationBar: navigationBar) } + + self.informationLabel.textColor = theme.textPrimaryColor + self.verificationWaitingLabel.textColor = theme.textSecondaryColor } private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) } + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in self?.cancelButtonAction() } self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.title = VectorL10n.deviceVerificationSelfVerifyWaitTitle + + self.verificationWaitingLabel.text = VectorL10n.deviceVerificationSelfVerifyWaitWaiting + self.informationLabel.text = VectorL10n.deviceVerificationSelfVerifyWaitInformation } - private func render(viewState: DeviceVerificationDataLoadingViewState) { + private func render(viewState: KeyVerificationSelfVerifyWaitViewState) { switch viewState { case .loading: self.renderLoading() @@ -98,8 +112,6 @@ final class DeviceVerificationDataLoadingViewController: UIViewController { self.renderLoaded() case .error(let error): self.render(error: error) - case .errorMessage(let message): - self.renderError(message: message) } } @@ -112,33 +124,22 @@ final class DeviceVerificationDataLoadingViewController: UIViewController { } private func render(error: Error) { - self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: { - self.viewModel.process(viewAction: .cancel) - }) + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) } - - private func renderError(message: String) { - self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: { - self.viewModel.process(viewAction: .cancel) - }) - } - + // MARK: - Actions - - @objc private func themeDidChange() { - self.update(theme: ThemeService.shared().theme) - } - + private func cancelButtonAction() { self.viewModel.process(viewAction: .cancel) } } -// MARK: - DeviceVerificationDataLoadingViewModelViewDelegate -extension DeviceVerificationDataLoadingViewController: DeviceVerificationDataLoadingViewModelViewDelegate { +// MARK: - KeyVerificationSelfVerifyWaitViewModelViewDelegate +extension KeyVerificationSelfVerifyWaitViewController: KeyVerificationSelfVerifyWaitViewModelViewDelegate { - func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didUpdateViewState viewSate: DeviceVerificationDataLoadingViewState) { + func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didUpdateViewState viewSate: KeyVerificationSelfVerifyWaitViewState) { self.render(viewState: viewSate) } } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift new file mode 100644 index 000000000..0f9755505 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift @@ -0,0 +1,122 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyVerificationSelfVerifyWaitViewModel: KeyVerificationSelfVerifyWaitViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationService: KeyVerificationService + private let verificationManager: MXKeyVerificationManager + +// private var verificationManager: MXKeyVerificationManager? + private var keyVerificationRequest: MXKeyVerificationRequest? + + // MARK: Public + + weak var viewDelegate: KeyVerificationSelfVerifyWaitViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession) { + self.session = session + self.verificationManager = session.crypto.keyVerificationManager + self.keyVerificationService = KeyVerificationService() + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: KeyVerificationSelfVerifyWaitViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .cancel: + self.cancel() + } + } + + // MARK: - Private + + private func loadData() { + self.registerKeyVerificationManagerNewRequestNotification(for: self.verificationManager) + } + + private func cancel() { + self.unregisterKeyVerificationManagerNewRequestNotification() + self.cancelKeyVerificationRequest() + self.coordinatorDelegate?.keyVerificationSelfVerifyWaitViewModelDidCancel(self) + } + + private func update(viewState: KeyVerificationSelfVerifyWaitViewState) { + self.viewDelegate?.keyVerificationSelfVerifyWaitViewModel(self, didUpdateViewState: viewState) + } + + private func cancelKeyVerificationRequest() { + self.keyVerificationRequest?.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) + } + + private func acceptKeyVerificationRequest(_ keyVerificationRequest: MXKeyVerificationRequest) { + + keyVerificationRequest.accept(withMethods: self.keyVerificationService.supportedKeyVerificationMethods(), success: { [weak self] in + guard let self = self else { + return + } + + self.coordinatorDelegate?.keyVerificationSelfVerifyWaitViewModel(self, didAcceptKeyVerificationRequest: keyVerificationRequest) + + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + }) + } + + // MARK: MXKeyVerificationManagerNewRequest + + private func registerKeyVerificationManagerNewRequestNotification(for verificationManager: MXKeyVerificationManager) { + NotificationCenter.default.addObserver(self, selector: #selector(keyVerificationManagerNewRequestNotification(notification:)), name: .MXKeyVerificationManagerNewRequest, object: verificationManager) + } + + private func unregisterKeyVerificationManagerNewRequestNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationManagerNewRequest, object: nil) + } + + @objc private func keyVerificationManagerNewRequestNotification(notification: Notification) { + + guard let userInfo = notification.userInfo, let keyVerificationRequest = userInfo[MXKeyVerificationManagerNotificationRequestKey] as? MXKeyVerificationByToDeviceRequest else { + return + } + + guard keyVerificationRequest.isFromMyUser, + keyVerificationRequest.isFromMyDevice == false, + keyVerificationRequest.state == MXKeyVerificationRequestStatePending else { + return + } + + self.acceptKeyVerificationRequest(keyVerificationRequest) + } +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift new file mode 100644 index 000000000..5cb34939e --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationSelfVerifyWaitViewModelViewDelegate: class { + func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didUpdateViewState viewSate: KeyVerificationSelfVerifyWaitViewState) +} + +protocol KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate: class { + func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) + func keyVerificationSelfVerifyWaitViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) +} + +/// Protocol describing the view model used by `KeyVerificationSelfVerifyWaitViewController` +protocol KeyVerificationSelfVerifyWaitViewModelType { + + var viewDelegate: KeyVerificationSelfVerifyWaitViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationSelfVerifyWaitViewAction) +} diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewState.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewState.swift new file mode 100644 index 000000000..7d0bed699 --- /dev/null +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationSelfVerifyWaitViewController view state +enum KeyVerificationSelfVerifyWaitViewState { + case loading + case loaded + case error(Error) +} diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartCoordinator.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinator.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartCoordinator.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinator.swift diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinatorType.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartCoordinatorType.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartCoordinatorType.swift diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewAction.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewAction.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewAction.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewAction.swift diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.storyboard b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewController.storyboard similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.storyboard rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewController.storyboard diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewController.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewController.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewController.swift diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewModel.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift similarity index 93% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewModel.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift index 092294795..2b2d4aecf 100644 --- a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewModel.swift +++ b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift @@ -25,7 +25,7 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy // MARK: Private private let session: MXSession - private let verificationManager: MXDeviceVerificationManager + private let verificationManager: MXKeyVerificationManager private let otherUser: MXUser private let otherDevice: MXDeviceInfo @@ -40,7 +40,7 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy init(session: MXSession, otherUser: MXUser, otherDevice: MXDeviceInfo) { self.session = session - self.verificationManager = session.crypto.deviceVerificationManager + self.verificationManager = session.crypto.keyVerificationManager self.otherUser = otherUser self.otherDevice = otherDevice } @@ -101,14 +101,14 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy } - // MARK: - MXDeviceVerificationTransactionDidChange + // MARK: - MXKeyVerificationTransactionDidChange private func registerTransactionDidStateChangeNotification(transaction: MXOutgoingSASTransaction) { - NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction) + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) } private func unregisterTransactionDidStateChangeNotification() { - NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil) + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) } @objc private func transactionDidStateChange(notification: Notification) { diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewModelType.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModelType.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewModelType.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModelType.swift diff --git a/Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewState.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewState.swift similarity index 100% rename from Riot/Modules/DeviceVerification/Start/DeviceVerificationStartViewState.swift rename to Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewState.swift diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinator.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinator.swift new file mode 100644 index 000000000..3a35dab77 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinator.swift @@ -0,0 +1,75 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class UserVerificationSessionStatusCoordinator: UserVerificationSessionStatusCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let userId: String + private let deviceId: String + private var userVerificationSessionStatusViewModel: UserVerificationSessionStatusViewModelType + private let userVerificationSessionStatusViewController: UserVerificationSessionStatusViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationSessionStatusCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.session = session + self.userId = userId + self.deviceId = deviceId + + let userVerificationSessionStatusViewModel = UserVerificationSessionStatusViewModel(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId) + let userVerificationSessionStatusViewController = UserVerificationSessionStatusViewController.instantiate(with: userVerificationSessionStatusViewModel) + self.userVerificationSessionStatusViewModel = userVerificationSessionStatusViewModel + self.userVerificationSessionStatusViewController = userVerificationSessionStatusViewController + } + + // MARK: - Public methods + + func start() { + self.userVerificationSessionStatusViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.userVerificationSessionStatusViewController + } +} + +// MARK: - UserVerificationSessionStatusViewModelCoordinatorDelegate +extension UserVerificationSessionStatusCoordinator: UserVerificationSessionStatusViewModelCoordinatorDelegate { + + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) { + self.delegate?.userVerificationSessionStatusCoordinator(self, wantsToManuallyVerifyDeviceWithId: deviceId, for: userId) + } + + func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType) { + self.delegate?.userVerificationSessionStatusCoordinatorDidClose(self) + } +} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift similarity index 52% rename from Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift rename to Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift index c70b8cc50..2808bd804 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift @@ -1,7 +1,7 @@ // File created from ScreenTemplate -// $ createScreen.sh DeviceVerification/Loading DeviceVerificationDataLoading +// $ createScreen.sh SessionStatus UserVerificationSessionStatus /* - Copyright 2019 New Vector Ltd + Copyright 2020 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,12 +18,12 @@ import Foundation -protocol DeviceVerificationDataLoadingCoordinatorDelegate: class { - func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) - func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) +protocol UserVerificationSessionStatusCoordinatorDelegate: class { + func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) + func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType) } -/// `DeviceVerificationDataLoadingCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. -protocol DeviceVerificationDataLoadingCoordinatorType: Coordinator, Presentable { - var delegate: DeviceVerificationDataLoadingCoordinatorDelegate? { get } +/// `UserVerificationSessionStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol UserVerificationSessionStatusCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationSessionStatusCoordinatorDelegate? { get } } diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewAction.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewAction.swift new file mode 100644 index 000000000..6c3f6b345 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionStatusViewController view actions exposed to view model +enum UserVerificationSessionStatusViewAction { + case loadData + case verify + case close +} diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.storyboard b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.storyboard new file mode 100644 index 000000000..a24c78be7 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.storyboard @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift new file mode 100644 index 000000000..01cdc04e0 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -0,0 +1,270 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class UserVerificationSessionStatusViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let verifyButtonCornerRadius: CGFloat = 8.0 + static let informationTextDefaultFont = UIFont.systemFont(ofSize: 15.0) + static let informationTextBoldFont = UIFont.systemFont(ofSize: 15.0, weight: .medium) + static let deviceNameFont = UIFont.systemFont(ofSize: 17.0, weight: .medium) + static let deviceIdFont = UIFont.systemFont(ofSize: 15.0) + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var badgeImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var closeButton: UIButton! + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var deviceStatusImageView: UIImageView! + @IBOutlet private weak var deviceInformationLabel: UILabel! + + @IBOutlet private weak var untrustedSessionContainerView: UIView! + @IBOutlet private weak var untrustedSessionInformationLabel: UILabel! + @IBOutlet private weak var verifyButton: UIButton! + + // MARK: Private + + private var viewModel: UserVerificationSessionStatusViewModelType! + private var theme: Theme! + private var keyboardAvoider: KeyboardAvoider? + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: UserVerificationSessionStatusViewModelType) -> UserVerificationSessionStatusViewController { + let viewController = StoryboardScene.UserVerificationSessionStatusViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.vc_removeBackTitle() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.verifyButton.layer.cornerRadius = Constants.verifyButtonCornerRadius + self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2 + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.view.backgroundColor = theme.headerBackgroundColor + + self.titleLabel.textColor = theme.textPrimaryColor + self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal) + + self.informationLabel.textColor = theme.textPrimaryColor + + self.untrustedSessionInformationLabel.textColor = theme.textPrimaryColor + self.verifyButton.vc_setBackgroundColor(theme.tintColor, for: .normal) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + self.closeButton.layer.masksToBounds = true + self.verifyButton.layer.masksToBounds = true + } + + private func render(viewState: UserVerificationSessionStatusViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(viewData: let sessionStatusViewData): + self.renderLoaded(viewData: sessionStatusViewData) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: SessionStatusViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + let badgeImage: UIImage + let title: String + + self.untrustedSessionContainerView.isHidden = viewData.isDeviceTrusted + + if viewData.isDeviceTrusted { + badgeImage = Asset.Images.encryptionTrusted.image + title = VectorL10n.userVerificationSessionDetailsTrustedTitle + } else { + badgeImage = Asset.Images.encryptionWarning.image + title = VectorL10n.userVerificationSessionDetailsUntrustedTitle + } + + let unstrustedInformationText: String + let verifyButtonTitle: String + + if viewData.isCurrentUser { + unstrustedInformationText = VectorL10n.userVerificationSessionDetailsAdditionalInformationUntrustedCurrentUser + verifyButtonTitle = VectorL10n.userVerificationSessionDetailsVerifyActionCurrentUser + } else { + unstrustedInformationText = VectorL10n.userVerificationSessionDetailsAdditionalInformationUntrustedOtherUser + verifyButtonTitle = VectorL10n.userVerificationSessionDetailsVerifyActionOtherUser + } + + self.badgeImageView.image = badgeImage + self.titleLabel.text = title + self.informationLabel.attributedText = self.buildInformationAttributedText(with: viewData) + + self.deviceStatusImageView.image = badgeImage + self.deviceInformationLabel.attributedText = self.builDeviceInfoAttributedText(with: viewData) + + self.untrustedSessionInformationLabel.text = unstrustedInformationText + self.verifyButton.setTitle(verifyButtonTitle, for: .normal) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func buildUserInfoText(with userId: String, userDisplayName: String?) -> String { + + let userInfoText: String + + if let userDisplayName = userDisplayName { + userInfoText = "\(userDisplayName) (\(userId))" + } else { + userInfoText = userId + } + + return userInfoText + } + + private func buildInformationAttributedText(with viewData: SessionStatusViewData) -> NSAttributedString { + + let informationAttributedText: NSMutableAttributedString = NSMutableAttributedString() + + let informationTextDefaultAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextDefaultFont] + + let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextBoldFont] + + let userInfoText = self.buildUserInfoText(with: viewData.userId, userDisplayName: viewData.userDisplayName) + + if viewData.isDeviceTrusted { + + if viewData.isCurrentUser { + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationTrustedCurrentUser, attributes: informationTextDefaultAttributes) + informationAttributedText.append(informationAttributedStringPart1) + } else { + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationTrustedOtherUserPart1, attributes: informationTextDefaultAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes) + let informationAttributedStringPart3 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationTrustedOtherUserPart2, attributes: informationTextDefaultAttributes) + + informationAttributedText.append(informationAttributedStringPart1) + informationAttributedText.append(informationAttributedStringPart2) + informationAttributedText.append(informationAttributedStringPart3) + } + + } else { + if viewData.isCurrentUser { + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationUntrustedCurrentUser, attributes: informationTextDefaultAttributes) + informationAttributedText.append(informationAttributedStringPart1) + } else { + let informationAttributedStringPart1 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationUntrustedOtherUser, attributes: informationTextDefaultAttributes) + + informationAttributedText.append(informationAttributedStringPart1) + informationAttributedText.append(informationAttributedStringPart2) + } + } + + return informationAttributedText + } + + private func builDeviceInfoAttributedText(with viewData: SessionStatusViewData) -> NSAttributedString { + let deviceInfoAttributedText = NSMutableAttributedString() + let deviceInfoAttributedTextPart1 = NSAttributedString(string: "\(viewData.deviceName) ", attributes: [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.deviceNameFont]) + let deviceInfoAttributedTextPart2 = NSAttributedString(string: "(\(viewData.deviceId))", attributes: [.foregroundColor: self.theme.textSecondaryColor, .font: Constants.deviceIdFont]) + deviceInfoAttributedText.append(deviceInfoAttributedTextPart1) + deviceInfoAttributedText.append(deviceInfoAttributedTextPart2) + return deviceInfoAttributedText + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .close) + } + + @IBAction private func verifyButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .verify) + } +} + + +// MARK: - UserVerificationSessionStatusViewModelViewDelegate +extension UserVerificationSessionStatusViewController: UserVerificationSessionStatusViewModelViewDelegate { + + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModel.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModel.swift new file mode 100644 index 000000000..621652cf6 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModel.swift @@ -0,0 +1,95 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct SessionStatusViewData { + let userId: String + let userDisplayName: String? + let isCurrentUser: Bool + + let deviceId: String + let deviceName: String + let isDeviceTrusted: Bool +} + +enum UserVerificationSessionStatusViewModelError: Error { + case deviceNotFound +} + +final class UserVerificationSessionStatusViewModel: UserVerificationSessionStatusViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let userId: String + private let userDisplayName: String? + private let deviceId: String + + // MARK: Public + + weak var viewDelegate: UserVerificationSessionStatusViewModelViewDelegate? + weak var coordinatorDelegate: UserVerificationSessionStatusViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + self.deviceId = deviceId + } + + // MARK: - Public + + func process(viewAction: UserVerificationSessionStatusViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .verify: + self.coordinatorDelegate?.userVerificationSessionStatusViewModel(self, wantsToManuallyVerifyDeviceWithId: self.deviceId, for: self.userId) + case .close: + self.coordinatorDelegate?.userVerificationSessionStatusViewModelDidClose(self) + } + } + + // MARK: - Private + + private func loadData() { + guard let deviceInfo = self.session.crypto.device(withDeviceId: self.deviceId, ofUser: self.userId) else { + self.update(viewState: .error(UserVerificationSessionStatusViewModelError.deviceNotFound)) + return + } + + let isCurrentUser = self.session.myUser.userId == self.userId + + let viewData = SessionStatusViewData(userId: self.userId, + userDisplayName: self.userDisplayName, + isCurrentUser: isCurrentUser, + deviceId: deviceInfo.deviceId, + deviceName: deviceInfo.displayName ?? "", + isDeviceTrusted: deviceInfo.trustLevel.isVerified) + self.update(viewState: .loaded(viewData: viewData)) + } + + private func update(viewState: UserVerificationSessionStatusViewState) { + self.viewDelegate?.userVerificationSessionStatusViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift new file mode 100644 index 000000000..178d0e415 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionStatusViewModelViewDelegate: class { + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState) +} + +protocol UserVerificationSessionStatusViewModelCoordinatorDelegate: class { + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) + func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType) +} + +/// Protocol describing the view model used by `UserVerificationSessionStatusViewController` +protocol UserVerificationSessionStatusViewModelType { + + var viewDelegate: UserVerificationSessionStatusViewModelViewDelegate? { get set } + var coordinatorDelegate: UserVerificationSessionStatusViewModelCoordinatorDelegate? { get set } + + func process(viewAction: UserVerificationSessionStatusViewAction) +} diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewState.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewState.swift new file mode 100644 index 000000000..f1eb8f1c6 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionStatusViewController view state +enum UserVerificationSessionStatusViewState { + case loading + case loaded(viewData: SessionStatusViewData) + case error(Error) +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionStatusCell.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionStatusCell.swift new file mode 100644 index 000000000..d0b6de6e7 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionStatusCell.swift @@ -0,0 +1,79 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import Reusable + +struct UserVerificationSessionStatusViewData { + let deviceId: String + let sessionName: String + let isTrusted: Bool +} + +final class UserVerificationSessionStatusCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var statusImageView: UIImageView! + @IBOutlet private weak var sessionNameLabel: UILabel! + @IBOutlet private weak var statusTextLabel: UILabel! + + // MARK: Private + + private var viewData: UserVerificationSessionStatusViewData? + private var theme: Theme? + + // MARK: - Public + + func fill(viewData: UserVerificationSessionStatusViewData) { + self.viewData = viewData + + let statusText: String + let statusImage: UIImage + + if viewData.isTrusted { + statusImage = Asset.Images.encryptionTrusted.image + statusText = VectorL10n.userVerificationSessionsListSessionTrusted + } else { + statusImage = Asset.Images.encryptionWarning.image + statusText = VectorL10n.userVerificationSessionsListSessionUntrusted + } + + self.statusImageView.image = statusImage + self.statusTextLabel.text = statusText + self.sessionNameLabel.text = viewData.sessionName + + self.updateStatusTextColor() + } + + func update(theme: Theme) { + self.theme = theme + self.backgroundColor = theme.headerBackgroundColor + self.sessionNameLabel.textColor = theme.textPrimaryColor + self.updateStatusTextColor() + } + + // MARK: - Private + + private func updateStatusTextColor() { + guard let viewData = self.viewData, let theme = self.theme else { + return + } + self.statusTextLabel.textColor = viewData.isTrusted ? theme.tintColor : theme.warningColor + } +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionStatusCell.xib b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionStatusCell.xib new file mode 100644 index 000000000..0ee3f3231 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionStatusCell.xib @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift new file mode 100644 index 000000000..b2d19c91c --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift @@ -0,0 +1,70 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var userVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType + private let userVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationSessionsStatusCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String) { + self.session = session + + let userVerificationSessionsStatusViewModel = UserVerificationSessionsStatusViewModel(session: self.session, userId: userId) + let userVerificationSessionsStatusViewController = UserVerificationSessionsStatusViewController.instantiate(with: userVerificationSessionsStatusViewModel) + self.userVerificationSessionsStatusViewModel = userVerificationSessionsStatusViewModel + self.userVerificationSessionsStatusViewController = userVerificationSessionsStatusViewController + } + + // MARK: - Public methods + + func start() { + self.userVerificationSessionsStatusViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.userVerificationSessionsStatusViewController + } +} + +// MARK: - UserVerificationSessionsStatusViewModelCoordinatorDelegate +extension UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusViewModelCoordinatorDelegate { + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) { + self.delegate?.userVerificationSessionsStatusCoordinator(self, didSelectDeviceWithId: deviceId, for: userId) + } + + func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) { + self.delegate?.userVerificationSessionsStatusCoordinatorDidClose(self) + } +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift new file mode 100644 index 000000000..46eccaaa5 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionsStatusCoordinatorDelegate: class { + func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType) + func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String) +} + +/// `UserVerificationSessionsStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol UserVerificationSessionsStatusCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationSessionsStatusCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewAction.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewAction.swift new file mode 100644 index 000000000..8487612ec --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionsStatusViewController view actions exposed to view model +enum UserVerificationSessionsStatusViewAction { + case loadData + case selectSession(deviceId: String) + case close +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard new file mode 100644 index 000000000..24f2aed27 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewController.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewController.swift new file mode 100644 index 000000000..b1384ba1f --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewController.swift @@ -0,0 +1,234 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class UserVerificationSessionsStatusViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let estimatedRowHeight: CGFloat = 40.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var badgeImageImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var closeButton: UIButton! + @IBOutlet private weak var informationLabel: UILabel! + @IBOutlet private weak var sessionsTableViewTitle: UILabel! + @IBOutlet private weak var tableView: UITableView! + + // MARK: Private + + private var viewModel: UserVerificationSessionsStatusViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityIndicatorPresenter: ActivityIndicatorPresenter! + private var sessionsStatusViewData: [UserVerificationSessionStatusViewData] = [] + private var userEncryptionTrustLevel: UserEncryptionTrustLevel = .unknown + + // MARK: - Setup + + class func instantiate(with viewModel: UserVerificationSessionsStatusViewModelType) -> UserVerificationSessionsStatusViewController { + let viewController = StoryboardScene.UserVerificationSessionsStatusViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.vc_removeBackTitle() + self.activityIndicatorPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2 + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let selectedIndexPath = self.tableView.indexPathForSelectedRow { + self.tableView.deselectRow(at: selectedIndexPath, animated: animated) + } + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal) + self.titleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textPrimaryColor + self.sessionsTableViewTitle.textColor = theme.textPrimaryColor + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + self.closeButton.layer.masksToBounds = true + self.setupTableView() + self.updateTitleViews() + + self.sessionsTableViewTitle.text = VectorL10n.userVerificationSessionsListTableTitle + self.informationLabel.text = VectorL10n.userVerificationSessionsListInformation + } + + private func setupTableView() { + self.tableView.rowHeight = UITableView.automaticDimension + self.tableView.estimatedRowHeight = Constants.estimatedRowHeight + self.tableView.separatorStyle = .none + self.tableView.tableFooterView = UIView() + self.tableView.alwaysBounceVertical = false + + self.tableView.register(cellType: UserVerificationSessionStatusCell.self) + } + + private func render(viewState: UserVerificationSessionsStatusViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(userTrustLevel: let userTrustLevel, sessionsStatusViewData: let sessionsStatusViewData): + self.renderLoaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.tableView.isUserInteractionEnabled = false + self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData]) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.tableView.isUserInteractionEnabled = true + + self.userEncryptionTrustLevel = userTrustLevel + self.sessionsStatusViewData = sessionsStatusViewData + + self.updateTitleViews() + self.tableView.reloadData() + } + + private func render(error: Error) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.tableView.isUserInteractionEnabled = true + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func updateTitleViews() { + + let badgeImage: UIImage + let title: String + + switch self.userEncryptionTrustLevel { + case .trusted: + badgeImage = Asset.Images.encryptionTrusted.image + title = VectorL10n.userVerificationSessionsListUserTrustLevelTrustedTitle + case .warning: + badgeImage = Asset.Images.encryptionWarning.image + title = VectorL10n.userVerificationSessionsListUserTrustLevelWarningTitle + default: + badgeImage = Asset.Images.encryptionNormal.image + title = VectorL10n.userVerificationSessionsListUserTrustLevelUnknownTitle + } + + self.badgeImageImageView.image = badgeImage + self.titleLabel.text = title + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .close) + } +} + +// MARK: - UITableViewDataSource +extension UserVerificationSessionsStatusViewController: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.sessionsStatusViewData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(for: indexPath, cellType: UserVerificationSessionStatusCell.self) + + let viewData = self.sessionsStatusViewData[indexPath.row] + + cell.update(theme: self.theme) + cell.fill(viewData: viewData) + + return cell + } +} + +// MARK: - UITableViewDelegate +extension UserVerificationSessionsStatusViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let viewData = self.sessionsStatusViewData[indexPath.row] + self.viewModel.process(viewAction: .selectSession(deviceId: viewData.deviceId)) + } +} + +// MARK: - UserVerificationSessionsStatusViewModelViewDelegate +extension UserVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewModelViewDelegate { + + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModel.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModel.swift new file mode 100644 index 000000000..27ecd1c09 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModel.swift @@ -0,0 +1,148 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum UserVerificationSessionsStatusViewModelError: Error { + case unknown +} + +final class UserVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let userId: String + private var currentOperation: MXHTTPOperation? + private var userTrustLevel: UserEncryptionTrustLevel + + // MARK: Public + + weak var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate? + weak var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String) { + self.session = session + self.userId = userId + self.userTrustLevel = .unknown + } + + deinit { + self.currentOperation?.cancel() + } + + // MARK: - Public + + func process(viewAction: UserVerificationSessionsStatusViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .selectSession(deviceId: let deviceId): + self.coordinatorDelegate?.userVerificationSessionsStatusViewModel(self, didSelectDeviceWithId: deviceId, for: self.userId) + case .close: + self.coordinatorDelegate?.userVerificationSessionsStatusViewModelDidClose(self) + } + } + + // MARK: - Private + + private func loadData() { + + let sessionsStatusViewData = self.getSessionStatusViewDataListFromCache(for: self.userId) + self.update(viewState: .loaded(userTrustLevel: self.userTrustLevel, sessionsStatusViewData: sessionsStatusViewData)) + + self.fetchSessionStatus() + } + + private func update(viewState: UserVerificationSessionsStatusViewState) { + self.viewDelegate?.userVerificationSessionsStatusViewModel(self, didUpdateViewState: viewState) + } + + private func fetchSessionStatus() { + self.update(viewState: .loading) + + self.currentOperation = self.getSessionStatusViewDataList(for: self.userId) { result in + switch result { + case .success(let sessionsStatusViewData): + + let isUserTrusted = sessionsStatusViewData.contains(where: { sessionsStatusViewData -> Bool in + return sessionsStatusViewData.isTrusted == false + }) == false + + let userTrustLevel: UserEncryptionTrustLevel = isUserTrusted ? .trusted : .warning + + self.update(viewState: .loaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData)) + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } + + private func getSessionStatusViewDataListFromCache(for userId: String) -> [UserVerificationSessionStatusViewData] { + let deviceInfoList = self.getDevicesFromCache(for: self.userId) + return self.sessionStatusViewDataList(from: deviceInfoList) + } + + private func getDevicesFromCache(for userId: String) -> [MXDeviceInfo] { + guard let deviceInfoMap = self.session.crypto.devices(forUser: self.userId) else { + return [] + } + return Array(deviceInfoMap.values) + } + + @discardableResult + private func getSessionStatusViewDataList(for userId: String, completion: @escaping (Result<[UserVerificationSessionStatusViewData], Error>) -> Void) -> MXHTTPOperation? { + + let httpOperation: MXHTTPOperation? + + httpOperation = self.session.crypto.downloadKeys([self.userId], forceDownload: false, success: { ( usersDeviceMap: MXUsersDevicesMap?, usersCrossSigningMap: [String : MXCrossSigningInfo]?) in + + let sessionsViewData: [UserVerificationSessionStatusViewData] + + if let usersDeviceMap = usersDeviceMap, let userDeviceInfoMap = Array(usersDeviceMap.map.values).first { + let deviceInfoList = Array(userDeviceInfoMap.values) + sessionsViewData = self.sessionStatusViewDataList(from: deviceInfoList) + } else { + sessionsViewData = [] + } + + completion(.success(sessionsViewData)) + + }, failure: { error in + + let finalError = error ?? UserVerificationSessionsStatusViewModelError.unknown + completion(.failure(finalError)) + }) + + return httpOperation + } + + private func sessionStatusViewData(from deviceInfo: MXDeviceInfo) -> UserVerificationSessionStatusViewData { + return UserVerificationSessionStatusViewData(deviceId: deviceInfo.deviceId, sessionName: deviceInfo.displayName ?? "", isTrusted: deviceInfo.trustLevel.isVerified) + } + + private func sessionStatusViewDataList(from deviceInfoList: [MXDeviceInfo]) -> [UserVerificationSessionStatusViewData] { + return deviceInfoList.map { (deviceInfo) -> UserVerificationSessionStatusViewData in + return self.sessionStatusViewData(from: deviceInfo) + } + } +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift new file mode 100644 index 000000000..4df41221d --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionsStatusViewModelViewDelegate: class { + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) +} + +protocol UserVerificationSessionsStatusViewModelCoordinatorDelegate: class { + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) + func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) +} + +/// Protocol describing the view model used by `UserVerificationSessionsStatusViewController` +protocol UserVerificationSessionsStatusViewModelType { + + var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate? { get set } + var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate? { get set } + + func process(viewAction: UserVerificationSessionsStatusViewAction) +} diff --git a/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewState.swift b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewState.swift new file mode 100644 index 000000000..742938c02 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/SessionsStatus/UserVerificationSessionsStatusViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionsStatusViewController view state +enum UserVerificationSessionsStatusViewState { + case loading + case loaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData]) + case error(Error) +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinator.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinator.swift new file mode 100644 index 000000000..47bdcdbb6 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinator.swift @@ -0,0 +1,74 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class UserVerificationStartCoordinator: UserVerificationStartCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomMember: MXRoomMember + + private var userVerificationStartViewModel: UserVerificationStartViewModelType + private let userVerificationStartViewController: UserVerificationStartViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationStartCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomMember: MXRoomMember) { + self.session = session + self.roomMember = roomMember + + let userVerificationStartViewModel = UserVerificationStartViewModel(session: self.session, roomMember: self.roomMember) + let userVerificationStartViewController = UserVerificationStartViewController.instantiate(with: userVerificationStartViewModel) + self.userVerificationStartViewModel = userVerificationStartViewModel + self.userVerificationStartViewController = userVerificationStartViewController + } + + // MARK: - Public methods + + func start() { + self.userVerificationStartViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.userVerificationStartViewController + } +} + +// MARK: - UserVerificationStartViewModelCoordinatorDelegate +extension UserVerificationStartCoordinator: UserVerificationStartViewModelCoordinatorDelegate { + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) { + self.delegate?.userVerificationStartCoordinator(self, otherDidAcceptRequest: request) + } + + func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType) { + self.delegate?.userVerificationStartCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift new file mode 100644 index 000000000..ba5c677d9 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartCoordinatorType.swift @@ -0,0 +1,31 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationStartCoordinatorDelegate: class { + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) + + func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType) +} + +/// `UserVerificationStartCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol UserVerificationStartCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationStartCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewAction.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewAction.swift new file mode 100644 index 000000000..3d0e50762 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationStartViewController view actions exposed to view model +enum UserVerificationStartViewAction { + case loadData + case startVerification + case cancel +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewController.storyboard b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewController.storyboard new file mode 100644 index 000000000..5af56d06b --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewController.storyboard @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewController.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewController.swift new file mode 100644 index 000000000..aa0e901f7 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewController.swift @@ -0,0 +1,230 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class UserVerificationStartViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let verifyButtonCornerRadius: CGFloat = 8.0 + static let informationTextDefaultFont = UIFont.systemFont(ofSize: 15.0) + static let informationTextBoldFont = UIFont.systemFont(ofSize: 15.0, weight: .medium) + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var startVerificationButton: UIButton! + @IBOutlet private weak var verificationWaitingLabel: UILabel! + + @IBOutlet private weak var additionalInformationLabel: UILabel! + + // MARK: Private + + private var viewModel: UserVerificationStartViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: UserVerificationStartViewModelType) -> UserVerificationStartViewController { + let viewController = StoryboardScene.UserVerificationStartViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.keyVerificationUserTitle + + self.setupViews() + + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.startVerificationButton.layer.cornerRadius = Constants.verifyButtonCornerRadius + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.informationLabel.textColor = theme.textPrimaryColor + self.startVerificationButton.vc_setBackgroundColor(theme.tintColor, for: .normal) + self.verificationWaitingLabel.textColor = theme.textSecondaryColor + self.additionalInformationLabel.textColor = theme.textSecondaryColor + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.startVerificationButton.layer.masksToBounds = true + self.startVerificationButton.setTitle(VectorL10n.userVerificationStartVerifyAction, for: .normal) + self.additionalInformationLabel.text = VectorL10n.userVerificationStartAdditionalInformation + } + + private func render(viewState: UserVerificationStartViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(let viewData): + self.renderLoaded(viewData: viewData) + case .error(let error): + self.render(error: error) + case .verificationPending: + self.renderVerificationPending() + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: UserVerificationStartViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.informationLabel.attributedText = self.buildInformationAttributedText(with: viewData.userId) + self.verificationWaitingLabel.text = self.buildVerificationWaitingText(with: viewData) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func renderVerificationPending() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.startVerificationButton.isHidden = true + self.verificationWaitingLabel.isHidden = false + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + private func buildInformationAttributedText(with userId: String) -> NSAttributedString { + + let informationAttributedText: NSMutableAttributedString = NSMutableAttributedString() + + let informationTextDefaultAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextDefaultFont] + + let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextBoldFont] + + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationStartInformationPart1, attributes: informationTextDefaultAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: userId, attributes: informationTextBoldAttributes) + let informationAttributedStringPart3 = NSAttributedString(string: VectorL10n.userVerificationStartInformationPart2, attributes: informationTextDefaultAttributes) + + informationAttributedText.append(informationAttributedStringPart1) + informationAttributedText.append(informationAttributedStringPart2) + informationAttributedText.append(informationAttributedStringPart3) + + return informationAttributedText + } + + private func buildVerificationWaitingText(with viewData: UserVerificationStartViewData) -> String { + let userName = viewData.userDisplayName ?? viewData.userId + return VectorL10n.userVerificationStartWaitingPartner(userName) + } + + // MARK: - Actions + + @IBAction private func startVerificationButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .startVerification) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - UserVerificationStartViewModelViewDelegate +extension UserVerificationStartViewController: UserVerificationStartViewModelViewDelegate { + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift new file mode 100644 index 000000000..9b9c1b6ff --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift @@ -0,0 +1,162 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum UserVerificationStartViewModelError: Error { + case keyVerificationRequestExpired +} + +struct UserVerificationStartViewData { + let userId: String + let userDisplayName: String? + let userAvatarURL: String? +} + +final class UserVerificationStartViewModel: UserVerificationStartViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomMember: MXRoomMember + private let verificationManager: MXKeyVerificationManager + private let keyVerificationService: KeyVerificationService + + private var keyVerificationRequest: MXKeyVerificationRequest? + + private var viewData: UserVerificationStartViewData { + return UserVerificationStartViewData(userId: self.roomMember.userId, userDisplayName: self.roomMember.displayname, userAvatarURL: self.roomMember.avatarUrl) + } + + // MARK: Public + + weak var viewDelegate: UserVerificationStartViewModelViewDelegate? + weak var coordinatorDelegate: UserVerificationStartViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomMember: MXRoomMember) { + self.session = session + self.verificationManager = session.crypto.keyVerificationManager + self.roomMember = roomMember + self.keyVerificationService = KeyVerificationService() + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: UserVerificationStartViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .startVerification: + self.startVerification() + case .cancel: + self.cancelKeyVerificationRequest() + self.coordinatorDelegate?.userVerificationStartViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + self.update(viewState: .loaded(self.viewData)) + } + + private func startVerification() { + self.update(viewState: .verificationPending) + + self.verificationManager.requestVerificationByDM(withUserId: self.roomMember.userId, + roomId: nil, + fallbackText: "", + methods: self.keyVerificationService.supportedKeyVerificationMethods(), + success: { [weak self] (keyVerificationRequest) in + guard let self = self else { + return + } + + self.keyVerificationRequest = keyVerificationRequest + self.update(viewState: .loaded(self.viewData)) + self.registerKeyVerificationRequestDidChangeNotification(for: keyVerificationRequest) + }, failure: { [weak self] error in + self?.update(viewState: .error(error)) + }) + } + + private func update(viewState: UserVerificationStartViewState) { + self.viewDelegate?.userVerificationStartViewModel(self, didUpdateViewState: viewState) + } + + private func cancelKeyVerificationRequest() { + guard let keyVerificationRequest = self.keyVerificationRequest else { + return + } + + keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) + } + + // MARK: - MXKeyVerificationRequestDidChange + + private func registerKeyVerificationRequestDidChangeNotification(for keyVerificationRequest: MXKeyVerificationRequest) { + NotificationCenter.default.addObserver(self, selector: #selector(keyVerificationRequestDidChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: keyVerificationRequest) + } + + private func unregisterKeyVerificationRequestDidChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil) + } + + @objc private func keyVerificationRequestDidChange(notification: Notification) { + guard let keyVerificationRequest = notification.object as? MXKeyVerificationByDMRequest else { + return + } + + guard let currentKeyVerificationRequest = self.keyVerificationRequest, keyVerificationRequest.requestId == currentKeyVerificationRequest.requestId else { + return + } + + switch keyVerificationRequest.state { + case MXKeyVerificationRequestStateAccepted: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.coordinatorDelegate?.userVerificationStartViewModel(self, otherDidAcceptRequest: currentKeyVerificationRequest) + case MXKeyVerificationRequestStateReady: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.coordinatorDelegate?.userVerificationStartViewModel(self, otherDidAcceptRequest: currentKeyVerificationRequest) + case MXKeyVerificationRequestStateCancelled: + guard let reason = keyVerificationRequest.reasonCancelCode else { + return + } + self.unregisterKeyVerificationRequestDidChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXKeyVerificationRequestStateCancelledByMe: + guard let reason = keyVerificationRequest.reasonCancelCode else { + return + } + self.unregisterKeyVerificationRequestDidChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + case MXKeyVerificationRequestStateExpired: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.update(viewState: .error(UserVerificationStartViewModelError.keyVerificationRequestExpired)) + default: + break + } + } +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift new file mode 100644 index 000000000..40582e0bf --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModelType.swift @@ -0,0 +1,39 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationStartViewModelViewDelegate: class { + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState) +} + +protocol UserVerificationStartViewModelCoordinatorDelegate: class { + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) + + func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType) +} + +/// Protocol describing the view model used by `UserVerificationStartViewController` +protocol UserVerificationStartViewModelType { + + var viewDelegate: UserVerificationStartViewModelViewDelegate? { get set } + var coordinatorDelegate: UserVerificationStartViewModelCoordinatorDelegate? { get set } + + func process(viewAction: UserVerificationStartViewAction) +} diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewState.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewState.swift new file mode 100644 index 000000000..efbb54c56 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewState.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationStartViewController view state +enum UserVerificationStartViewState { + case loading + case loaded(UserVerificationStartViewData) + case verificationPending + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift new file mode 100644 index 000000000..a3cc53577 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift @@ -0,0 +1,191 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh UserVerification UserVerification +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let presenter: Presentable + private let navigationRouter: NavigationRouterType + private let session: MXSession + private let userId: String + private let userDisplayName: String? + private var deviceId: String? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationCoordinatorDelegate? + + // MARK: - Setup + + init(presenter: Presentable, session: MXSession, userId: String, userDisplayName: String?) { + self.presenter = presenter + self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + } + + convenience init(presenter: Presentable, session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.init(presenter: presenter, session: session, userId: userId, userDisplayName: userDisplayName) + self.deviceId = deviceId + } + + // MARK: - Public methods + + func start() { + // Do not start again if existing coordinators are presented + guard self.childCoordinators.isEmpty else { + return + } + + guard self.session.crypto.crossSigning.canCrossSign else { + self.presentBootstrapNotSetup() + return + } + + let rootCoordinator: Coordinator & Presentable + + if let deviceId = self.deviceId { + rootCoordinator = self.createSessionStatusCoordinator(with: deviceId, for: self.userId, userDisplayName: self.userDisplayName) + } else { + rootCoordinator = self.createUserVerificationSessionsStatusCoordinator() + } + + rootCoordinator.start() + + self.add(childCoordinator: rootCoordinator) + + self.navigationRouter.setRootModule(rootCoordinator, hideNavigationBar: true, animated: false, popCompletion: { + self.remove(childCoordinator: rootCoordinator) + }) + + let rootViewController = self.navigationRouter.toPresentable() + rootViewController.modalPresentationStyle = .formSheet + + self.presenter.toPresentable().present(rootViewController, animated: true, completion: nil) + } + + func toPresentable() -> UIViewController { + return self.navigationRouter.toPresentable() + } + + // MARK: - Private methods + + private func createUserVerificationSessionsStatusCoordinator() -> UserVerificationSessionsStatusCoordinator { + let coordinator = UserVerificationSessionsStatusCoordinator(session: self.session, userId: self.userId) + coordinator.delegate = self + return coordinator + } + + private func createSessionStatusCoordinator(with deviceId: String, for userId: String, userDisplayName: String?) -> UserVerificationSessionStatusCoordinator { + let coordinator = UserVerificationSessionStatusCoordinator(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId) + coordinator.delegate = self + return coordinator + } + + private func presentSessionStatus(with deviceId: String, for userId: String, userDisplayName: String?) { + let coordinator = self.createSessionStatusCoordinator(with: deviceId, for: userId, userDisplayName: userDisplayName) + coordinator.start() + + self.navigationRouter.push(coordinator, animated: true) { + self.remove(childCoordinator: coordinator) + } + } + + private func presentDeviceVerification(for deviceId: String) { + + let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyDevice(userId: self.userId, deviceId: deviceId), navigationRouter: self.navigationRouter) + keyVerificationCoordinator.delegate = self + keyVerificationCoordinator.start() + + self.add(childCoordinator: keyVerificationCoordinator) + + self.navigationRouter.push(keyVerificationCoordinator, animated: true, popCompletion: { + self.remove(childCoordinator: keyVerificationCoordinator) + }) + } + + private func presentBootstrapNotSetup() { + let alert = UIAlertController(title: VectorL10n.keyVerificationBootstrapNotSetupTitle, + message: VectorL10n.keyVerificationBootstrapNotSetupMessage, + preferredStyle: .alert) + + let cancelAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "ok"), style: .cancel, handler: { _ in + }) + alert.addAction(cancelAction) + + self.presenter.toPresentable().present(alert, animated: true, completion: nil) + } +} + +// MARK: - UserVerificationSessionsStatusCoordinatorDelegate +extension UserVerificationCoordinator: UserVerificationSessionsStatusCoordinatorDelegate { + func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType) { + + self.presenter.toPresentable().dismiss(animated: true) { + self.remove(childCoordinator: coordinator) + } + } + + func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String) { + self.presentSessionStatus(with: deviceId, for: userId, userDisplayName: self.userDisplayName) + } +} + +// MARK: - UserVerificationSessionStatusCoordinatorDelegate +extension UserVerificationCoordinator: UserVerificationSessionStatusCoordinatorDelegate { + + func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) { + + self.presentDeviceVerification(for: deviceId) + } + + func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType) { + + self.presenter.toPresentable().dismiss(animated: true) { + self.remove(childCoordinator: coordinator) + } + } +} + +// MARK: - UserVerificationCoordinatorDelegate +extension UserVerificationCoordinator: KeyVerificationCoordinatorDelegate { + + func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { + dismissPresenter(coordinator: coordinator) + } + + func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) { + dismissPresenter(coordinator: coordinator) + } + + func dismissPresenter(coordinator: KeyVerificationCoordinatorType) { + self.presenter.toPresentable().dismiss(animated: true) { + self.remove(childCoordinator: coordinator) + } + } +} diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..af19e72f3 --- /dev/null +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorBridgePresenter.swift @@ -0,0 +1,84 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh UserVerification UserVerification +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objc protocol UserVerificationCoordinatorBridgePresenterDelegate { + func userVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: UserVerificationCoordinatorBridgePresenter) +} + +/// UserVerificationCoordinatorBridgePresenter enables to start UserVerificationCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class UserVerificationCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let presenter: Presentable + private let session: MXSession + private let userId: String + private let userDisplayName: String? + private var deviceId: String? + + private var coordinator: Coordinator? + + // MARK: Public + + weak var delegate: UserVerificationCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(presenter: UIViewController, session: MXSession, userId: String, userDisplayName: String?) { + self.presenter = presenter + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + super.init() + } + + init(presenter: UIViewController, session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.presenter = presenter + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + self.deviceId = deviceId + super.init() + } + + // MARK: - Public + + func start() { + self.present() + } + + func present() { + + let userVerificationCoordinator: UserVerificationCoordinator + + if let deviceId = self.deviceId { + userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName, deviceId: deviceId) + } else { + userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName) + } + + userVerificationCoordinator.start() + self.coordinator = userVerificationCoordinator + } +} diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift new file mode 100644 index 000000000..feeba8d3b --- /dev/null +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinatorType.swift @@ -0,0 +1,28 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh UserVerification UserVerification +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationCoordinatorDelegate: class { + func userVerificationCoordinatorDidComplete(_ coordinator: UserVerificationCoordinatorType) +} + +/// `UserVerificationCoordinatorType` is a protocol describing a Coordinator that handle user verification navigation flow. +protocol UserVerificationCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/QRCode/QRCodeGenerator.swift b/Riot/Modules/QRCode/QRCodeGenerator.swift new file mode 100644 index 000000000..f3ad32c05 --- /dev/null +++ b/Riot/Modules/QRCode/QRCodeGenerator.swift @@ -0,0 +1,50 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class QRCodeGenerator { + + // MARK: - Constants + + private enum Constants { + static let qrCodeGeneratorFilter = "CIQRCodeGenerator" + static let qrCodeInputCorrectionLevel = "M" + } + + // MARK: - Public + + func generateCode(from data: Data, with size: CGSize) -> UIImage? { + guard let filter = CIFilter(name: Constants.qrCodeGeneratorFilter) else { + return nil + } + + filter.setValue(data, forKey: "inputMessage") + filter.setValue(Constants.qrCodeInputCorrectionLevel, forKey: "inputCorrectionLevel") // Be sure to use same error resilience level as other platform + + guard let ciImage = filter.outputImage else { + return nil + } + + let scaleX = size.width/ciImage.extent.size.width + let scaleY = size.height/ciImage.extent.size.height + + let transform = CGAffineTransform(scaleX: scaleX, y: scaleY) + + let transformedCIImage = ciImage.transformed(by: transform) + return UIImage(ciImage: transformedCIImage) + } +} diff --git a/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.storyboard b/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.storyboard new file mode 100644 index 000000000..8a084200a --- /dev/null +++ b/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.storyboard @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift b/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift new file mode 100644 index 000000000..c3bb92fde --- /dev/null +++ b/Riot/Modules/QRCode/Reader/QRCodeReaderViewController.swift @@ -0,0 +1,270 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import ZXingObjC + +protocol QRCodeReaderViewControllerDelegate: class { + func qrCodeReaderViewController(_ viewController: QRCodeReaderViewController, didFound payloadData: Data) + func qrCodeReaderViewControllerDidCancel(_ viewController: QRCodeReaderViewController) +} + +/// QRCodeReaderViewController is a view controller used to scan a QR code +/// Some methods are based on [ZXing sample](https://github.com/zxingify/zxingify-objc/blob/master/examples/BarcodeScannerSwift/BarcodeScannerSwift/ViewController.swift) +final class QRCodeReaderViewController: UIViewController { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var closeButton: CloseButton! + @IBOutlet private weak var codeReaderContainerView: UIView! + + // MARK: Private + + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + + private lazy var zxCapture: ZXCapture = ZXCapture() + private var captureSizeTransform: CGAffineTransform? + private var isScanning: Bool = false + private var isFirstApplyOrientation: Bool = false + + // MARK: Public + + weak var delegate: QRCodeReaderViewControllerDelegate? + + // MARK: - Setup + + class func instantiate() -> QRCodeReaderViewController { + let viewController = StoryboardScene.QRCodeReaderViewController.initialScene.instantiate() + viewController.theme = ThemeService.shared().theme + return viewController + } + + deinit { + self.stopScanning() + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.startScanning() + } + + override public func viewWillDisappear(_ animated: Bool) { + self.stopScanning() + + super.viewWillDisappear(animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + guard isFirstApplyOrientation == false else { + return + } + + isFirstApplyOrientation = true + applyOrientation() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: { (context) in + // do nothing + }, completion: { [weak self] (context) in + guard let self = self else { + return + } + self.applyOrientation() + }) + } + + // MARK: - Public + + func startScanning() { + self.zxCapture.start() + isScanning = true + } + + func stopScanning() { + self.zxCapture.stop() + isScanning = false + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.closeButton.update(theme: theme) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + self.setupQRCodeReaderView() + } + + private func setupQRCodeReaderView() { + zxCapture.delegate = self + zxCapture.camera = zxCapture.back() + + zxCapture.layer.frame = codeReaderContainerView.bounds + codeReaderContainerView.layer.addSublayer(zxCapture.layer) + } + + private func applyOrientation() { + + let orientation = UIApplication.shared.statusBarOrientation + let captureRotation: Double + let scanRectRotation: Double + + switch orientation { + case .portrait: + captureRotation = 0 + scanRectRotation = 90 + case .landscapeLeft: + captureRotation = 90 + scanRectRotation = 180 + case .landscapeRight: + captureRotation = 270 + scanRectRotation = 0 + case .portraitUpsideDown: + captureRotation = 180 + scanRectRotation = 270 + default: + captureRotation = 0 + scanRectRotation = 90 + } + + applyRectOfInterest(orientation: orientation) + + let angleRadius = captureRotation / 180.0 * Double.pi + let captureTranform = CGAffineTransform(rotationAngle: CGFloat(angleRadius)) + + zxCapture.transform = captureTranform + zxCapture.rotation = CGFloat(scanRectRotation) + zxCapture.layer.frame = codeReaderContainerView.frame + } + + private func applyRectOfInterest(orientation: UIInterfaceOrientation) { + guard var transformedVideoRect = codeReaderContainerView?.frame, + let cameraSessionPreset = zxCapture.sessionPreset + else { return } + + var scaleVideoX, scaleVideoY: CGFloat + var videoHeight, videoWidth: CGFloat + + // Currently support only for 1920x1080 || 1280x720 + if cameraSessionPreset == AVCaptureSession.Preset.hd1920x1080.rawValue { + videoHeight = 1080.0 + videoWidth = 1920.0 + } else { + videoHeight = 720.0 + videoWidth = 1280.0 + } + + if orientation == UIInterfaceOrientation.portrait { + scaleVideoX = self.view.frame.width / videoHeight + scaleVideoY = self.view.frame.height / videoWidth + + // Convert CGPoint under portrait mode to map with orientation of image + // because the image will be cropped before rotate + // reference: https://github.com/TheLevelUp/ZXingObjC/issues/222 + let realX = transformedVideoRect.origin.y + let realY = self.view.frame.size.width - transformedVideoRect.size.width - transformedVideoRect.origin.x + let realWidth = transformedVideoRect.size.height + let realHeight = transformedVideoRect.size.width + transformedVideoRect = CGRect(x: realX, y: realY, width: realWidth, height: realHeight) + + } else { + scaleVideoX = self.view.frame.width / videoWidth + scaleVideoY = self.view.frame.height / videoHeight + } + + captureSizeTransform = CGAffineTransform(scaleX: 1.0/scaleVideoX, y: 1.0/scaleVideoY) + + guard let _captureSizeTransform = captureSizeTransform else { + return + } + + let transformRect = transformedVideoRect.applying(_captureSizeTransform) + zxCapture.scanRect = transformRect + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.delegate?.qrCodeReaderViewControllerDidCancel(self) + } +} + +// MARK: - ZXCaptureDelegate +extension QRCodeReaderViewController: ZXCaptureDelegate { + + func captureCameraIsReady(_ capture: ZXCapture!) { + isScanning = true + } + + func captureResult(_ capture: ZXCapture!, result: ZXResult!) { + guard let zxResult = result, isScanning == true else { + return + } + + guard zxResult.barcodeFormat == kBarcodeFormatQRCode else { + return + } + + self.stopScanning() + + if let bytes = result.resultMetadata.object(forKey: kResultMetadataTypeByteSegments.rawValue) as? NSArray, + let byteArray = bytes.firstObject as? ZXByteArray { + + let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length)) + + self.delegate?.qrCodeReaderViewController(self, didFound: data) + } + } +} diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index 7c34278ff..62d27d998 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -21,7 +21,11 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) { RoomBubbleCellDataTagMessage = 0, // Default value used for messages RoomBubbleCellDataTagMembership, - RoomBubbleCellDataTagRoomCreateWithPredecessor + RoomBubbleCellDataTagRoomCreateWithPredecessor, + RoomBubbleCellDataTagKeyVerificationNoDisplay, + RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval, + RoomBubbleCellDataTagKeyVerificationRequest, + RoomBubbleCellDataTagKeyVerificationConclusion }; /** @@ -70,6 +74,16 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) */ @property(nonatomic, readonly) CGFloat additionalContentHeight; +/** + MXKeyVerification object associated to key verifcation event when using key verification by direct message. + */ +@property(nonatomic, strong) MXKeyVerification *keyVerification; + +/** + Indicate if there is a pending operation that updates `keyVerification` property. + */ +@property(nonatomic) BOOL isKeyVerificationOperationPending; + /** Indicate to update additional content height. */ diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index a89214ce4..af30837de 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -62,27 +62,35 @@ static NSAttributedString *timestampVerticalWhitespace = nil; if (self) { - if (event.eventType == MXEventTypeRoomMember) + switch (event.eventType) { - // Membership events have their own cell type - self.tag = RoomBubbleCellDataTagMembership; - - // Membership events can be collapsed together - self.collapsable = YES; - - // Collapse them by default - self.collapsed = YES; + case MXEventTypeRoomMember: + { + // Membership events have their own cell type + self.tag = RoomBubbleCellDataTagMembership; + + // Membership events can be collapsed together + self.collapsable = YES; + + // Collapse them by default + self.collapsed = YES; + } + break; + case MXEventTypeRoomCreate: + { + MXRoomCreateContent *createContent = [MXRoomCreateContent modelFromJSON:event.content]; + + if (createContent.roomPredecessorInfo) + { + self.tag = RoomBubbleCellDataTagRoomCreateWithPredecessor; + } + } + break; + default: + break; } - if (event.eventType == MXEventTypeRoomCreate) - { - MXRoomCreateContent *createContent = [MXRoomCreateContent modelFromJSON:event.content]; - - if (createContent.roomPredecessorInfo) - { - self.tag = RoomBubbleCellDataTagRoomCreateWithPredecessor; - } - } + [self keyVerificationDidUpdate]; // Increase maximum number of components self.maxComponentCount = 20; @@ -147,6 +155,16 @@ static NSAttributedString *timestampVerticalWhitespace = nil; return attributedTextMessage; } +- (BOOL)hasNoDisplay +{ + if (self.tag == RoomBubbleCellDataTagKeyVerificationNoDisplay) + { + return YES; + } + + return [super hasNoDisplay]; +} + #pragma mark - Bubble collapsing - (BOOL)collapseWith:(id)cellData @@ -663,21 +681,154 @@ static NSAttributedString *timestampVerticalWhitespace = nil; - (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState { - if (self.tag == RoomBubbleCellDataTagMembership || event.eventType == MXEventTypeRoomMember) + BOOL shouldAddEvent = YES; + + switch (self.tag) { - // One single bubble per membership event - return NO; + case RoomBubbleCellDataTagKeyVerificationNoDisplay: + case RoomBubbleCellDataTagKeyVerificationRequest: + case RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval: + case RoomBubbleCellDataTagKeyVerificationConclusion: + shouldAddEvent = NO; + break; + case RoomBubbleCellDataTagRoomCreateWithPredecessor: + // We do not want to merge room create event cells with other cell types + shouldAddEvent = NO; + break; + case RoomBubbleCellDataTagMembership: + // One single bubble per membership event + shouldAddEvent = NO; + break; + default: + break; } - if (self.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor || event.eventType == MXEventTypeRoomCreate) + if (shouldAddEvent) { - // We do not want to merge room create event cells with other cell types - return NO; + switch (event.eventType) + { + case MXEventTypeRoomMessage: + { + NSString *messageType = event.content[@"msgtype"]; + + if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + shouldAddEvent = NO; + } + } + break; + case MXEventTypeKeyVerificationStart: + case MXEventTypeKeyVerificationAccept: + case MXEventTypeKeyVerificationKey: + case MXEventTypeKeyVerificationMac: + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + shouldAddEvent = NO; + break; + case MXEventTypeRoomMember: + shouldAddEvent = NO; + break; + case MXEventTypeRoomCreate: + shouldAddEvent = NO; + break; + default: + break; + } } - - return [super addEvent:event andRoomState:roomState]; + + if (shouldAddEvent) + { + shouldAddEvent = [super addEvent:event andRoomState:roomState]; + } + + return shouldAddEvent; } +- (void)setKeyVerification:(MXKeyVerification *)keyVerification +{ + _keyVerification = keyVerification; + + [self keyVerificationDidUpdate]; +} + +- (void)keyVerificationDidUpdate +{ + MXEvent *event = self.getFirstBubbleComponentWithDisplay.event; + MXKeyVerification *keyVerification = _keyVerification; + + if (!event) + { + return; + } + + switch (event.eventType) + { + case MXEventTypeKeyVerificationCancel: + { + RoomBubbleCellDataTag cellDataTag; + + MXTransactionCancelCode *transactionCancelCode = keyVerification.transaction.reasonCancelCode; + + if (transactionCancelCode + && ([transactionCancelCode isEqual:[MXTransactionCancelCode mismatchedSas]] + || [transactionCancelCode isEqual:[MXTransactionCancelCode mismatchedKeys]] + || [transactionCancelCode isEqual:[MXTransactionCancelCode mismatchedCommitment]] + ) + ) + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationConclusion; + } + else + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationNoDisplay; + } + + self.tag = cellDataTag; + } + break; + case MXEventTypeKeyVerificationDone: + { + RoomBubbleCellDataTag cellDataTag; + + // Avoid to display incoming and outgoing done, only display the incoming one. + if (self.isIncoming && keyVerification && (keyVerification.state == MXKeyVerificationStateVerified)) + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationConclusion; + } + else + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationNoDisplay; + } + + self.tag = cellDataTag; + } + break; + case MXEventTypeRoomMessage: + { + NSString *msgType = event.content[@"msgtype"]; + + if ([msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + RoomBubbleCellDataTag cellDataTag; + + if (self.isIncoming && !self.isKeyVerificationOperationPending && keyVerification && keyVerification.state == MXKeyVerificationRequestStatePending) + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval; + } + else + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationRequest; + } + + self.tag = cellDataTag; + } + } + break; + default: + break; + } + +} #pragma mark - Show all reactions diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift index 7913799cd..082e368e4 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift @@ -103,13 +103,13 @@ final class ReactionsMenuView: UIView, Themable, NibLoadable { private func fill(reactionsMenuViewDatas: [ReactionMenuItemViewData]) { self.reactionViewDatas = reactionsMenuViewDatas - self.reactionsStackView.vc_removeAllSubviews() + self.reactionsStackView.vc_removeAllArrangedSubviews() let reactionsStackViewCount = self.reactionsStackView.arrangedSubviews.count // Remove all menu buttons if reactions count has changed if reactionsStackViewCount != self.reactionViewDatas.count { - self.reactionsStackView.vc_removeAllSubviews() + self.reactionsStackView.vc_removeAllArrangedSubviews() } var index = 0 diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift index 8d695c945..9ef0436e5 100644 --- a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift @@ -53,7 +53,7 @@ final class RoomContextualMenuToolbarView: MXKRoomInputToolbarView, NibOwnerLoad } @objc func fill(contextualMenuItems: [RoomContextualMenuItem]) { - self.menuItemsStackView.vc_removeAllSubviews() + self.menuItemsStackView.vc_removeAllArrangedSubviews() self.menuItemViews.removeAll() for menuItem in contextualMenuItems { diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index 1c7dfda5a..37ec55cbf 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -19,6 +19,10 @@ #import "WidgetManager.h" +#import "MXRoomSummary+Riot.h" + +@protocol RoomDataSourceDelegate; + /** The data source for `RoomViewController` in Vector. */ @@ -39,6 +43,11 @@ */ @property(nonatomic) BOOL showBubbleDateTimeOnSelection; +/** + Current room members trust level for an encrypted room. + */ +@property(nonatomic, readonly) RoomEncryptionTrustLevel encryptionTrustLevel; + /** Check if there is an active jitsi widget in the room and return it. @@ -62,4 +71,32 @@ success:(void (^)(NSString *eventId))success failure:(void (^)(NSError *error))failure; +/** + Accept incoming key verification request. + + @param eventId Event id associated to the key verification request event. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)acceptVerificationRequestForEventId:(NSString*)eventId + success:(void(^)(void))success + failure:(void(^)(NSError*))failure; + +/** + Decline incoming key verification request. + + @param eventId Event id associated to the key verification request event. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)declineVerificationRequestForEventId:(NSString*)eventId + success:(void(^)(void))success + failure:(void(^)(NSError*))failure; + +@end + +@protocol RoomDataSourceDelegate + +- (void)roomDataSource:(RoomDataSource*)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel; + @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 103fd0acc..e1122a84e 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -35,6 +35,19 @@ id kThemeServiceDidChangeThemeNotificationObserver; } +// Observe key verification request changes +@property (nonatomic, weak) id keyVerificationRequestDidChangeNotificationObserver; + +// Observe key verification transaction changes +@property (nonatomic, weak) id keyVerificationTransactionDidChangeNotificationObserver; + +// Timer used to debounce cells refresh +@property (nonatomic, strong) NSTimer *refreshCellsTimer; + +@property (nonatomic, readonly) id roomDataSourceDelegate; + +@property(nonatomic, readwrite) RoomEncryptionTrustLevel encryptionTrustLevel; + @end @implementation RoomDataSource @@ -71,6 +84,12 @@ [self reload]; }]; + + [self registerKeyVerificationRequestNotification]; + [self registerKeyVerificationTransactionNotification]; + [self registerTrustLevelDidChangeNotifications]; + + self.encryptionTrustLevel = RoomEncryptionTrustLevelUnknown; } return self; } @@ -93,6 +112,21 @@ NSLog(@"[MXKRoomDataSource] finalizeRoomDataSource: Cannot retrieve all room members"); }]; } + + if (self.room.summary.isEncrypted) + { + [self fetchEncryptionTrustedLevel]; + } +} + +- (id)roomDataSourceDelegate +{ + if (!self.delegate || ![self.delegate conformsToProtocol:@protocol(RoomDataSourceDelegate)]) + { + return nil; + } + + return ((id)(self.delegate)); } - (void)updateEventFormatter @@ -117,6 +151,16 @@ kThemeServiceDidChangeThemeNotificationObserver = nil; } + if (self.keyVerificationRequestDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:self.keyVerificationRequestDidChangeNotificationObserver]; + } + + if (self.keyVerificationTransactionDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:self.keyVerificationTransactionDidChangeNotificationObserver]; + } + [super destroy]; } @@ -145,6 +189,31 @@ } } +#pragma mark Encryption trust level + +- (void)registerTrustLevelDidChangeNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(roomSummaryDidChange:) name:kMXRoomSummaryDidChangeNotification object:self.room.summary]; +} + + +- (void)roomSummaryDidChange:(NSNotification*)notification +{ + if (!self.room.summary.isEncrypted) + { + return; + } + + [self fetchEncryptionTrustedLevel]; +} + +- (void)fetchEncryptionTrustedLevel +{ + self.encryptionTrustLevel = self.room.summary.roomEncryptionTrustLevel; + [self.roomDataSourceDelegate roomDataSource:self didUpdateEncryptionTrustLevel:self.encryptionTrustLevel]; +} + + #pragma mark - - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section @@ -189,6 +258,8 @@ { roomBubbleCellData.senderAvatarPlaceholder = [AvatarGenerator generateAvatarForMatrixItem:roomBubbleCellData.senderId withDisplayName:roomBubbleCellData.senderDisplayName]; } + + [self updateKeyVerificationIfNeededForRoomBubbleCellData:roomBubbleCellData]; UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; @@ -270,7 +341,7 @@ CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; - if (self.room.summary.isEncrypted) + if (roomBubbleCellData.containsBubbleComponentWithEncryptionBadge) { leftMargin+= RoomBubbleCellLayout.encryptedContentLeftMargin; } @@ -284,7 +355,7 @@ ]]; } - MXKReceiptSendersContainer* avatarsContainer; + MXKReceiptSendersContainer* avatarsContainer; // Handle read receipts (if any) if (self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) @@ -349,47 +420,57 @@ { [bubbleCell.tmpSubviews addObject:avatarsContainer]; } - [bubbleCell.contentView addSubview:avatarsContainer]; - // Force receipts container size - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:RoomBubbleCellLayout.readReceiptsViewWidth]; - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:RoomBubbleCellLayout.readReceiptsViewHeight]; - - // Force receipts container position - NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:avatarsContainer.superview - attribute:NSLayoutAttributeTrailing - multiplier:1.0 - constant:-RoomBubbleCellLayout.readReceiptsViewRightMargin]; - - // At the bottom, we have reactions or nothing - NSLayoutConstraint *topConstraint; - if (reactionsView) + if ([[bubbleCell class] conformsToProtocol:@protocol(BubbleCellReadReceiptsDisplayable)]) { - topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:reactionsView.bottomAnchor constant:RoomBubbleCellLayout.readReceiptsViewTopMargin]; + id readReceiptsDisplayable = (id)bubbleCell; + + [readReceiptsDisplayable addReadReceiptsView:avatarsContainer]; } else { - topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:avatarsContainer.superview.topAnchor constant:bottomPositionY + RoomBubbleCellLayout.readReceiptsViewTopMargin]; + [bubbleCell.contentView addSubview:avatarsContainer]; + + // Force receipts container size + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:RoomBubbleCellLayout.readReceiptsViewWidth]; + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:RoomBubbleCellLayout.readReceiptsViewHeight]; + + // Force receipts container position + NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:avatarsContainer.superview + attribute:NSLayoutAttributeTrailing + multiplier:1.0 + constant:-RoomBubbleCellLayout.readReceiptsViewRightMargin]; + + // At the bottom, we have reactions or nothing + NSLayoutConstraint *topConstraint; + if (reactionsView) + { + topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:reactionsView.bottomAnchor constant:RoomBubbleCellLayout.readReceiptsViewTopMargin]; + } + else + { + topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:avatarsContainer.superview.topAnchor constant:bottomPositionY + RoomBubbleCellLayout.readReceiptsViewTopMargin]; + } + + + // Available on iOS 8 and later + [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; } - - - // Available on iOS 8 and later - [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; } } @@ -516,6 +597,173 @@ return cell; } +- (RoomBubbleCellData*)roomBubbleCellDataForEventId:(NSString*)eventId +{ + id cellData = [self cellDataOfEventWithEventId:eventId]; + RoomBubbleCellData *roomBubbleCellData; + + if ([cellData isKindOfClass:RoomBubbleCellData.class]) + { + roomBubbleCellData = (RoomBubbleCellData*)cellData; + } + + return roomBubbleCellData; +} + +- (MXKeyVerificationRequest*)keyVerificationRequestFromEventId:(NSString*)eventId +{ + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:eventId]; + + return roomBubbleCellData.keyVerification.request; +} + +- (void)refreshCellsWithDelay +{ + if (self.refreshCellsTimer) + { + return; + } + + self.refreshCellsTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(refreshCellsTimerFired) userInfo:nil repeats:NO]; +} + +- (void)refreshCellsTimerFired +{ + [self refreshCells]; + self.refreshCellsTimer = nil; +} + +- (void)refreshCells +{ + if (self.delegate) + { + [self.delegate dataSource:self didCellChange:nil]; + } +} + +- (void)registerKeyVerificationRequestNotification +{ + self.keyVerificationRequestDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXKeyVerificationRequestDidChangeNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notification) + { + id notificationObject = notification.object; + + if ([notificationObject isKindOfClass:MXKeyVerificationByDMRequest.class]) + { + MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)notificationObject; + + if ([keyVerificationByDMRequest.roomId isEqualToString:self.roomId]) + { + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:keyVerificationByDMRequest.eventId]; + + roomBubbleCellData.isKeyVerificationOperationPending = NO; + roomBubbleCellData.keyVerification = nil; + + if (roomBubbleCellData) + { + [self refreshCellsWithDelay]; + } + } + } + }]; +} + +- (void)registerKeyVerificationTransactionNotification +{ + self.keyVerificationTransactionDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXKeyVerificationTransactionDidChangeNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notification) + { + MXKeyVerificationTransaction *keyVerificationTransaction = (MXKeyVerificationTransaction*)notification.object; + + if ([keyVerificationTransaction.dmRoomId isEqualToString:self.roomId]) + { + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:keyVerificationTransaction.dmEventId]; + + roomBubbleCellData.isKeyVerificationOperationPending = NO; + roomBubbleCellData.keyVerification = nil; + + if (roomBubbleCellData) + { + [self refreshCellsWithDelay]; + } + } + }]; +} + +- (BOOL)shouldFetchKeyVerificationForEvent:(MXEvent*)event +{ + if (!event) + { + return NO; + } + + BOOL shouldFetchKeyVerification = NO; + + switch (event.eventType) + { + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + shouldFetchKeyVerification = YES; + break; + case MXEventTypeRoomMessage: + { + NSString *msgType = event.content[@"msgtype"]; + + if ([msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + shouldFetchKeyVerification = YES; + } + } + break; + default: + break; + } + + return shouldFetchKeyVerification; +} + +- (void)updateKeyVerificationIfNeededForRoomBubbleCellData:(RoomBubbleCellData*)bubbleCellData +{ + MXEvent *event = bubbleCellData.getFirstBubbleComponentWithDisplay.event; + + if (![self shouldFetchKeyVerificationForEvent:event]) + { + return; + } + + if (bubbleCellData.keyVerification != nil || bubbleCellData.isKeyVerificationOperationPending) + { + // Key verification already fetched or request is pending do nothing + return; + } + + __block MXHTTPOperation *operation = [self.mxSession.crypto.keyVerificationManager keyVerificationFromKeyVerificationEvent:event + success:^(MXKeyVerification * _Nonnull keyVerification) + { + BOOL shouldRefreshCells = bubbleCellData.isKeyVerificationOperationPending || bubbleCellData.keyVerification == nil; + + bubbleCellData.keyVerification = keyVerification; + bubbleCellData.isKeyVerificationOperationPending = NO; + + if (shouldRefreshCells) + { + [self refreshCellsWithDelay]; + } + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[RoomDataSource] updateKeyVerificationIfNeededForRoomBubbleCellData; keyVerificationFromKeyVerificationEvent fails with error: %@", error); + + bubbleCellData.isKeyVerificationOperationPending = NO; + }]; + + bubbleCellData.isKeyVerificationOperationPending = !operation; +} + #pragma mark - - (void)setSelectedEventId:(NSString *)selectedEventId @@ -567,6 +815,68 @@ [self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure]; } +- (void)acceptVerificationRequestForEventId:(NSString*)eventId success:(void(^)(void))success failure:(void(^)(NSError*))failure +{ + MXKeyVerificationRequest *keyVerificationRequest = [self keyVerificationRequestFromEventId:eventId]; + + if (!keyVerificationRequest) + { + NSError *error; + + if (failure) + { + failure(error); + } + return; + } + + [[AppDelegate theDelegate] presentIncomingKeyVerificationRequest:keyVerificationRequest inSession:self.mxSession]; + + if (success) + { + success(); + } +} + +- (void)declineVerificationRequestForEventId:(NSString*)eventId success:(void(^)(void))success failure:(void(^)(NSError*))failure +{ + MXKeyVerificationRequest *keyVerificationRequest = [self keyVerificationRequestFromEventId:eventId]; + + if (!keyVerificationRequest) + { + NSError *error; + + if (failure) + { + failure(error); + } + return; + } + + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:eventId]; + roomBubbleCellData.isKeyVerificationOperationPending = YES; + + [self refreshCells]; + + [keyVerificationRequest cancelWithCancelCode:MXTransactionCancelCode.user success:^{ + + // roomBubbleCellData.isKeyVerificationOperationPending will be set to NO by MXKeyVerificationRequestDidChangeNotification notification + + if (success) + { + success(); + } + + } failure:^(NSError * _Nonnull error) { + + roomBubbleCellData.isKeyVerificationOperationPending = NO; + + if (failure) + { + failure(error); + } + }]; +} #pragma - Accessibility diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h index dd318b926..9881e328b 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h @@ -18,18 +18,6 @@ #import "DeviceTableViewCell.h" -@interface RoomMemberDetailsViewController : MXKRoomMemberDetailsViewController +@interface RoomMemberDetailsViewController : MXKRoomMemberDetailsViewController -@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarHeaderBackground; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomMemberAvatarHeaderBackgroundHeightConstraint; - -@property (weak, nonatomic) IBOutlet UIView *memberHeaderView; -@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarMask; -@property (weak, nonatomic) IBOutlet UILabel *roomMemberNameLabel; -@property (weak, nonatomic) IBOutlet UIView *roomMemberNameLabelMask; - -@property (weak, nonatomic) IBOutlet UILabel *roomMemberStatusLabel; - -@property (weak, nonatomic) IBOutlet UIImageView *bottomImageView; @end - diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index d84d669ec..3f4399f55 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -28,15 +28,18 @@ #import "TableViewCellWithButton.h" #import "RoomTableViewCell.h" +#import "MXRoom+Riot.h" #define TABLEVIEW_ROW_CELL_HEIGHT 46 #define TABLEVIEW_SECTION_HEADER_HEIGHT 28 -#define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f -@interface RoomMemberDetailsViewController () +@interface RoomMemberDetailsViewController () { RoomMemberTitleView* memberTitleView; + NSInteger securityIndex; + NSArray *securityActionsArray; + /** List of the admin actions on this member. */ @@ -60,7 +63,7 @@ */ NSArray *devicesArray; NSInteger devicesIndex; - DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter; + KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; /** @@ -78,6 +81,28 @@ */ BOOL isStatusBarHidden; } + +@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarHeaderBackground; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomMemberAvatarHeaderBackgroundHeightConstraint; + +@property (weak, nonatomic) IBOutlet UIView *memberHeaderView; +@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarMask; +@property (weak, nonatomic) IBOutlet UIImageView *roomMemberAvatarBadgeImageView; + +@property (weak, nonatomic) IBOutlet UILabel *roomMemberNameLabel; +@property (weak, nonatomic) IBOutlet UIView *roomMemberNameLabelMask; + +@property (weak, nonatomic) IBOutlet UILabel *roomMemberStatusLabel; + +@property (weak, nonatomic) IBOutlet UIImageView *bottomImageView; + +@property (weak, nonatomic) IBOutlet UILabel *roomMemberPowerLevelLabel; +@property (weak, nonatomic) IBOutlet UIView *roomMemberPowerLevelContainerView; + +@property(nonatomic) UserEncryptionTrustLevel encryptionTrustLevel; + +@property(nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter; + @end @implementation RoomMemberDetailsViewController @@ -105,6 +130,7 @@ // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; + self.encryptionTrustLevel = UserEncryptionTrustLevelUnknown; adminActionsArray = [[NSMutableArray alloc] init]; otherActionsArray = [[NSMutableArray alloc] init]; @@ -195,10 +221,15 @@ [self.tableView registerClass:TableViewCellWithButton.class forCellReuseIdentifier:[TableViewCellWithButton defaultReuseIdentifier]]; [self.tableView registerClass:RoomTableViewCell.class forCellReuseIdentifier:[RoomTableViewCell defaultReuseIdentifier]]; [self.tableView registerClass:DeviceTableViewCell.class forCellReuseIdentifier:[DeviceTableViewCell defaultReuseIdentifier]]; + [self.tableView registerClass:MXKTableViewCell.class forCellReuseIdentifier:[MXKTableViewCell defaultReuseIdentifier]]; // Hide line separators of empty cells self.tableView.tableFooterView = [[UIView alloc] init]; + // Enable self sizing cells + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 50; + // Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg. UIApplicationWillChangeStatusBarOrientationNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarOrientationNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -226,6 +257,7 @@ self.roomMemberNameLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; self.roomMemberStatusLabel.textColor = ThemeService.shared.theme.tintColor; + self.roomMemberPowerLevelLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); @@ -262,6 +294,8 @@ // Handle here the bottom image visibility UIInterfaceOrientation screenOrientation = [[UIApplication sharedApplication] statusBarOrientation]; self.bottomImageView.hidden = (screenOrientation == UIInterfaceOrientationLandscapeLeft || screenOrientation == UIInterfaceOrientationLandscapeRight); + + [self refreshUserEncryptionTrustLevel]; } - (void)viewWillDisappear:(BOOL)animated @@ -357,39 +391,56 @@ { self.roomMemberNameLabel.text = self.mxRoomMember.displayname ? self.mxRoomMember.displayname : self.mxRoomMember.userId; - // Update member badge + // Update member power level MXWeakify(self); [self.mxRoom state:^(MXRoomState *roomState) { MXStrongifyAndReturnIfNil(self); + NSString *powerLevelTextFormat; + MXRoomPowerLevels *powerLevels = [roomState powerLevels]; NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; - if (powerLevel >= RoomPowerLevelAdmin) - { - self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"admin_icon"]; - self->memberTitleView.memberBadge.hidden = NO; + + RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel]; + + switch (roomPowerLevel) { + case RoomPowerLevelAdmin: + powerLevelTextFormat = NSLocalizedStringFromTable(@"room_member_power_level_admin_in", @"Vector", nil); + break; + case RoomPowerLevelModerator: + powerLevelTextFormat = NSLocalizedStringFromTable(@"room_member_power_level_moderator_in", @"Vector", nil); + break; + default: + powerLevelTextFormat = nil; + break; } - else if (powerLevel >= RoomPowerLevelModerator) + + NSString *powerLevelText; + + if (powerLevelTextFormat) { - self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"mod_icon"]; - self->memberTitleView.memberBadge.hidden = NO; - } - else - { - self->memberTitleView.memberBadge.hidden = YES; + NSString *roomName = self.mxRoom.summary.displayname; + powerLevelText = [NSString stringWithFormat:powerLevelTextFormat, roomName]; } + + self.roomMemberPowerLevelLabel.text = powerLevelText; + self.roomMemberPowerLevelContainerView.hidden = !powerLevelTextFormat; }]; NSString* presenceText; - if (self.mxRoomMember.userId) + NSString *userId = self.mxRoomMember.userId; + + if (userId) { - MXUser *user = [self.mxRoom.mxSession userWithUserId:self.mxRoomMember.userId]; + MXUser *user = [self.mxRoom.mxSession userWithUserId:userId]; presenceText = [Tools presenceText:user]; } self.roomMemberStatusLabel.text = presenceText; + self.roomMemberAvatarBadgeImageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:self.encryptionTrustLevel]; + // Retrieve the existing direct chats [directChatsArray removeAllObjects]; NSArray *directRoomIds = self.mainSession.directRooms[self.mxRoomMember.userId]; @@ -401,41 +452,45 @@ [directChatsArray addObject:directRoomId]; } } - - // Retrieve member's devices - NSString *userId = self.mxRoomMember.userId; - __weak typeof(self) weakSelf = self; - - [self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { - - if (weakSelf) - { - // Restore the status bar - typeof(self) self = weakSelf; - self->devicesArray = usersDevicesInfoMap.map[userId].allValues; - // Reload the full table to take into account a potential change on a device status. - [super updateMemberInfo]; - } - - } failure:^(NSError *error) { - - NSLog(@"[RoomMemberDetailsVC] Crypto failed to download device info for user: %@", userId); - if (weakSelf) - { - // Restore the status bar - typeof(self) self = weakSelf; - // Notify the end user - NSString *myUserId = self.mainSession.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - } - - }]; } // Complete data update and reload table view [super updateMemberInfo]; } +- (void)refreshUserEncryptionTrustLevel +{ + NSString *userId = self.mxRoomMember.userId; + + if (!userId) + { + return; + } + + self.encryptionTrustLevel = [self.mxRoom encryptionTrustLevelForUserId:userId]; + [self updateMemberInfo]; +} + +- (BOOL)isRoomMemberCurrentUser +{ + return [self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId]; +} + +- (void)startUserVerification +{ + [[AppDelegate theDelegate] presentUserVerificationForRoomMember:self.mxRoomMember session:self.mainSession]; +} + +- (void)presentUserVerification +{ + UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter = [[UserVerificationCoordinatorBridgePresenter alloc] initWithPresenter:self + session:self.mxRoom.mxSession + userId:self.mxRoomMember.userId + userDisplayName:self.mxRoomMember.displayname]; + [userVerificationCoordinatorBridgePresenter start]; + self.userVerificationCoordinatorBridgePresenter = userVerificationCoordinatorBridgePresenter; +} + #pragma mark - Hide/Show navigation bar border - (void)hideNavigationBarBorder:(BOOL)isHidden @@ -484,7 +539,7 @@ [otherActionsArray removeAllObjects]; // Consider the case of the user himself - if ([self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId]) + if (self.isRoomMemberCurrentUser) { isOneself = YES; @@ -618,7 +673,24 @@ } } - adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1; + if (self.mxRoom.summary.isEncrypted) + { + securityActionsArray = @[@(MXKRoomMemberDetailsActionSecurity), + @(MXKRoomMemberDetailsActionSecurityInformation)]; + + } + else + { + securityActionsArray = @[@(MXKRoomMemberDetailsActionSecurity)]; + } + + securityIndex = adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1; + + + if (securityActionsArray.count) + { + securityIndex = sectionCount++; + } if (otherActionsArray.count) { @@ -644,7 +716,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (section == adminToolsIndex) + if (section == securityIndex) + { + return securityActionsArray.count; + } + else if (section == adminToolsIndex) { return adminActionsArray.count; } @@ -666,10 +742,18 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if (section == adminToolsIndex) + if (section == securityIndex) + { + return NSLocalizedStringFromTable(@"room_participants_action_section_security", @"Vector", nil); + } + else if (section == adminToolsIndex) { return NSLocalizedStringFromTable(@"room_participants_action_section_admin_tools", @"Vector", nil); } + else if (section == otherActionsIndex) + { + return NSLocalizedStringFromTable(@"room_participants_action_section_other", @"Vector", nil); + } else if (section == directChatsIndex) { return NSLocalizedStringFromTable(@"room_participants_action_section_direct_chats", @"Vector", nil); @@ -741,7 +825,87 @@ { UITableViewCell *cell; - if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex) + if (indexPath.section == securityIndex && indexPath.row < securityActionsArray.count) + { + NSNumber *actionNumber = securityActionsArray[indexPath.row]; + + if (actionNumber.unsignedIntegerValue == MXKRoomMemberDetailsActionSecurity) + { + MXKTableViewCell *securityStatusCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; + + NSString *statusText; + + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelTrusted: + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_verified", @"Vector", nil); + break; + case UserEncryptionTrustLevelNormal: + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_verify", @"Vector", nil); + break; + case UserEncryptionTrustLevelWarning: + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_warning", @"Vector", nil); + break; + default: + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_loading", @"Vector", nil); + break; + } + + securityStatusCell.imageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:self.encryptionTrustLevel]; + + securityStatusCell.textLabel.numberOfLines = 1; + securityStatusCell.textLabel.font = [UIFont systemFontOfSize:16.0]; + securityStatusCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + securityStatusCell.textLabel.text = statusText; + + securityStatusCell.backgroundColor = ThemeService.shared.theme.backgroundColor; + securityStatusCell.contentView.backgroundColor = [UIColor clearColor]; + securityStatusCell.selectionStyle = UITableViewCellSelectionStyleNone; + securityStatusCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + cell = securityStatusCell; + } + else if (actionNumber.unsignedIntegerValue == MXKRoomMemberDetailsActionSecurityInformation) + { + MXKTableViewCell *encryptionInfoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; + + NSMutableString *encryptionInformation = [NSMutableString new]; + + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelWarning: + case UserEncryptionTrustLevelNormal: + case UserEncryptionTrustLevelTrusted: + [encryptionInformation appendString:NSLocalizedStringFromTable(@"room_participants_security_information_room_encrypted", @"Vector", nil)]; + break; + case UserEncryptionTrustLevelNone: + [encryptionInformation appendString:NSLocalizedStringFromTable(@"room_participants_security_information_room_not_encrypted", @"Vector", nil)]; + break; + case UserEncryptionTrustLevelUnknown: + [encryptionInformation appendString:NSLocalizedStringFromTable(@"room_participants_security_information_loading", @"Vector", nil)]; + break; + default: + break; + } + + if (encryptionInformation.length) + { + [encryptionInformation appendString:@"\n"]; + } + + encryptionInfoCell.textLabel.backgroundColor = [UIColor clearColor]; + encryptionInfoCell.textLabel.numberOfLines = 0; + encryptionInfoCell.textLabel.text = encryptionInformation; + encryptionInfoCell.textLabel.font = [UIFont systemFontOfSize:14.0]; + encryptionInfoCell.textLabel.textColor = ThemeService.shared.theme.headerTextPrimaryColor; + + encryptionInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + encryptionInfoCell.accessoryType = UITableViewCellAccessoryNone; + encryptionInfoCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + encryptionInfoCell.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + + cell = encryptionInfoCell; + } + } + else if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex) { TableViewCellWithButton *cellWithButton = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath]; @@ -852,36 +1016,25 @@ } } -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == directChatsIndex) - { - return [RoomTableViewCell cellHeight]; - } - else if (indexPath.section == devicesIndex) - { - if (indexPath.row < devicesArray.count) - { - return [DeviceTableViewCell cellHeightWithDeviceInfo:devicesArray[indexPath.row] andCellWidth:self.tableView.frame.size.width]; - } - } - - return TABLEVIEW_ROW_CELL_HEIGHT; -} - - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section == otherActionsIndex) - { - return TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN; - } - return TABLEVIEW_SECTION_HEADER_HEIGHT; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - if (indexPath.section == directChatsIndex) + if (indexPath.section == securityIndex) + { + if (self.encryptionTrustLevel == UserEncryptionTrustLevelNormal) + { + [self startUserVerification]; + } + else + { + [self presentUserVerification]; + } + } + else if (indexPath.section == directChatsIndex) { if (indexPath.row < directChatsArray.count) { @@ -1140,10 +1293,10 @@ { if (verificationStatus == MXDeviceVerified) { - deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; - deviceVerificationCoordinatorBridgePresenter.delegate = self; + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; - [deviceVerificationCoordinatorBridgePresenter presentFrom:self otherUserId:deviceTableViewCell.deviceInfo.userId otherDeviceId:deviceTableViewCell.deviceInfo.deviceId animated:YES]; + [keyVerificationCoordinatorBridgePresenter presentFrom:self otherUserId:deviceTableViewCell.deviceInfo.userId otherDeviceId:deviceTableViewCell.deviceInfo.deviceId animated:YES]; } else { @@ -1163,12 +1316,22 @@ [self viewDidLayoutSubviews]; } -#pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate +#pragma mark - KeyVerificationCoordinatorBridgePresenterDelegate -- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { - [deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; - deviceVerificationCoordinatorBridgePresenter = nil; + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ + [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + keyVerificationCoordinatorBridgePresenter = nil; } @end diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib index b03f8eec4..999ddf2fc 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib @@ -1,12 +1,11 @@ - + - - + @@ -15,11 +14,14 @@ + + + @@ -31,7 +33,7 @@ - + @@ -45,7 +47,7 @@ - + @@ -53,11 +55,20 @@ + + + + + + + + + @@ -72,6 +83,14 @@ + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + - + - + @@ -97,16 +136,19 @@ + + + - + @@ -129,7 +171,7 @@ - + @@ -157,6 +199,7 @@ + diff --git a/Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h b/Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h new file mode 100644 index 000000000..ae9471461 --- /dev/null +++ b/Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h @@ -0,0 +1,28 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +@import Foundation; + +/** + UserEncryptionTrustLevel represents the user trust level in an encrypted room. + */ +typedef NS_ENUM(NSUInteger, UserEncryptionTrustLevel) { + UserEncryptionTrustLevelTrusted, + UserEncryptionTrustLevelWarning, + UserEncryptionTrustLevelNormal, + UserEncryptionTrustLevelNone, + UserEncryptionTrustLevelUnknown +}; diff --git a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h index e81d3faf7..009b77e22 100644 --- a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h +++ b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h @@ -49,7 +49,6 @@ + (instancetype)roomMemberTitleView; @property (weak, nonatomic) IBOutlet UIView *memberAvatarMask; -@property (weak, nonatomic) IBOutlet UIImageView *memberBadge; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *memberAvatarMaskCenterXConstraint; /** diff --git a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib index b86be3ba7..50eaf408d 100644 --- a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib +++ b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib @@ -1,11 +1,11 @@ - + - + @@ -15,16 +15,8 @@ - - + @@ -35,17 +27,14 @@ - - - diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index 6944e0900..270bd94cb 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -1127,20 +1127,28 @@ { MXRoomState *roomState = self.mxRoom.dangerousSyncState; - // Update member badge + // Update member power level MXRoomPowerLevels *powerLevels = [roomState powerLevels]; NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:contact.mxMember.userId]; - if (powerLevel >= RoomPowerLevelAdmin) - { - participantCell.thumbnailBadgeView.image = [UIImage imageNamed:@"admin_icon"]; - participantCell.thumbnailBadgeView.hidden = NO; - } - else if (powerLevel >= RoomPowerLevelModerator) - { - participantCell.thumbnailBadgeView.image = [UIImage imageNamed:@"mod_icon"]; - participantCell.thumbnailBadgeView.hidden = NO; + + RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel]; + + NSString *powerLevelText; + + switch (roomPowerLevel) { + case RoomPowerLevelAdmin: + powerLevelText = NSLocalizedStringFromTable(@"room_member_power_level_short_admin", @"Vector", nil); + break; + case RoomPowerLevelModerator: + powerLevelText = NSLocalizedStringFromTable(@"room_member_power_level_short_moderator", @"Vector", nil); + break; + default: + powerLevelText = nil; + break; } + participantCell.powerLevelLabel.text = powerLevelText; + // Update the contact display name by considering the current room state. if (contact.mxMember.userId) { diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 2c4fac795..781970e12 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -125,7 +125,8 @@ @interface RoomViewController () + ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate, + RoomDataSourceDelegate> { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -353,6 +354,14 @@ [self.bubblesTableView registerClass:RoomSelectedStickerBubbleCell.class forCellReuseIdentifier:RoomSelectedStickerBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:RoomPredecessorBubbleCell.class forCellReuseIdentifier:RoomPredecessorBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationRequestStatusBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationConclusionBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationConclusionWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + + // Prepare expanded header expandedHeader = [ExpandedRoomTitleView roomTitleView]; expandedHeader.delegate = self; @@ -1243,6 +1252,13 @@ [self cancelEventSelection]; } +- (void)setRoomTitleViewClass:(Class)roomTitleViewClass +{ + [super setRoomTitleViewClass:roomTitleViewClass]; + + [self updateTitleViewEncryptionDecoration]; +} + - (void)destroy { rightBarButtonItems = nil; @@ -1413,6 +1429,11 @@ return NO; } +- (BOOL)isEncryptionEnabled +{ + return self.roomDataSource.room.summary.isEncrypted && self.mainSession.crypto != nil; +} + - (void)refreshRoomTitle { if (rightBarButtonItems && !self.navigationItem.rightBarButtonItems) @@ -1533,12 +1554,8 @@ roomInputToolbarView.supportCallOption &= ([[AppDelegate theDelegate] callStatusBarWindow] == nil); } - // Check whether the encryption is enabled in the room - if (self.roomDataSource.room.summary.isEncrypted) - { - // Encrypt the user's messages as soon as the user supports the encryption? - roomInputToolbarView.isEncryptionEnabled = (self.mainSession.crypto != nil); - } + // Update encryption decoration if needed + [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; } else if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:DisabledRoomInputToolbarView.class]) { @@ -1618,6 +1635,54 @@ [UIView setAnimationsEnabled:YES]; } +- (UIImage*)roomEncryptionBadgeImage +{ + UIImage *encryptionIcon; + + if (self.isEncryptionEnabled) + { + RoomEncryptionTrustLevel roomEncryptionTrustLevel = ((RoomDataSource*)self.roomDataSource).encryptionTrustLevel; + + encryptionIcon = [EncryptionTrustLevelBadgeImageHelper roomBadgeImageFor:roomEncryptionTrustLevel]; + } + + return encryptionIcon; +} + +- (void)updateInputToolbarEncryptionDecoration +{ + if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + { + RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; + } +} + +- (void)updateExpandedHeaderEncryptionDecoration +{ + if (self->expandedHeader) + { + self->expandedHeader.roomAvatarBadgeImageView.image = self.roomEncryptionBadgeImage; + } +} + +- (void)updateTitleViewEncryptionDecoration +{ + if (![self.titleView isKindOfClass:[RoomTitleView class]]) + { + return; + } + + RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView; + roomTitleView.badgeImageView.image = self.roomEncryptionBadgeImage; +} + +- (void)updateEncryptionDecorationForRoomInputToolbar:(RoomInputToolbarView*)roomInputToolbarView +{ + roomInputToolbarView.isEncryptionEnabled = self.isEncryptionEnabled; + roomInputToolbarView.encryptedRoomIcon.image = self.roomEncryptionBadgeImage; +} + - (void)handleLongPressFromCell:(id)cell withTappedEvent:(MXEvent*)event { if (event && !customizedRoomDataSource.selectedEventId) @@ -1717,9 +1782,12 @@ // Note the avatar title view does not define tap gesture. expandedHeader.roomAvatar.alpha = 0.0; + expandedHeader.roomAvatarBadgeImageView.alpha = 0.0; shadowImage = [[UIImage alloc] init]; + [self updateExpandedHeaderEncryptionDecoration]; + // Dismiss the keyboard when header is expanded. [self.inputToolbarView dismissKeyboard]; } @@ -1750,7 +1818,8 @@ self.bubblesTableViewTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top : 0); self.jumpToLastUnreadBannerContainerTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant : self.bubblesTableView.mxk_adjustedContentInset.top); - expandedHeader.roomAvatar.alpha = 1; + self->expandedHeader.roomAvatar.alpha = 1; + self->expandedHeader.roomAvatarBadgeImageView.alpha = 1; // Force to render the view [self forceLayoutRefresh]; @@ -2023,13 +2092,21 @@ - (Class)cellViewClassForCellData:(MXKCellData*)cellData { Class cellViewClass = nil; - BOOL isEncryptedRoom = self.roomDataSource.room.summary.isEncrypted; + BOOL showEncryptionBadge = NO; // Sanity check if ([cellData conformsToProtocol:@protocol(MXKRoomBubbleCellDataStoring)]) { id bubbleData = (id)cellData; + MXKRoomBubbleCellData *roomBubbleCellData; + + if ([bubbleData isKindOfClass:MXKRoomBubbleCellData.class]) + { + roomBubbleCellData = (MXKRoomBubbleCellData*)bubbleData; + showEncryptionBadge = roomBubbleCellData.containsBubbleComponentWithEncryptionBadge; + } + // Select the suitable table view cell class, by considering first the empty bubble cell. if (bubbleData.hasNoDisplay) { @@ -2039,6 +2116,18 @@ { cellViewClass = RoomPredecessorBubbleCell.class; } + else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval) + { + cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class : KeyVerificationIncomingRequestApprovalBubbleCell.class; + } + else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequest) + { + cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class : KeyVerificationRequestStatusBubbleCell.class; + } + else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationConclusion) + { + cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationConclusionWithPaginationTitleBubbleCell.class : KeyVerificationConclusionBubbleCell.class; + } else if (bubbleData.tag == RoomBubbleCellDataTagMembership) { if (bubbleData.collapsed) @@ -2075,15 +2164,15 @@ } else if (bubbleData.isPaginationFirstBubble) { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class; } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class; } else { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class; } } else @@ -2092,24 +2181,24 @@ { if (bubbleData.shouldHideSenderName) { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class; } else { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class; } } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class; } else if (bubbleData.shouldHideSenderName) { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class; } else { - cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedTextMsgBubbleCell.class : RoomIncomingTextMsgBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgBubbleCell.class : RoomIncomingTextMsgBubbleCell.class; } } } @@ -2125,15 +2214,15 @@ } else if (bubbleData.isPaginationFirstBubble) { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class :RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class :RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class; } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class; } else { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedAttachmentBubbleCell.class : RoomOutgoingAttachmentBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentBubbleCell.class : RoomOutgoingAttachmentBubbleCell.class; } } else @@ -2142,24 +2231,24 @@ { if (bubbleData.shouldHideSenderName) { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class; } else { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class; } } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class :RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class :RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class; } else if (bubbleData.shouldHideSenderName) { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class; } else { - cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedTextMsgBubbleCell.class : RoomOutgoingTextMsgBubbleCell.class; + cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgBubbleCell.class : RoomOutgoingTextMsgBubbleCell.class; } } } @@ -2255,6 +2344,30 @@ [self showContextualMenuForEvent:selectedEvent fromSingleTapGesture:YES cell:cell animated:YES]; } } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed]) + { + NSString *eventId = userInfo[kMXKRoomBubbleCellEventIdKey]; + + RoomDataSource *roomDataSource = (RoomDataSource*)self.roomDataSource; + + [roomDataSource acceptVerificationRequestForEventId:eventId success:^{ + + } failure:^(NSError *error) { + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed]) + { + NSString *eventId = userInfo[kMXKRoomBubbleCellEventIdKey]; + + RoomDataSource *roomDataSource = (RoomDataSource*)self.roomDataSource; + + [roomDataSource declineVerificationRequestForEventId:eventId success:^{ + + } failure:^(NSError *error) { + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView]) { if (((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventSentState == MXEventSentStateFailed) @@ -3149,6 +3262,15 @@ return roomInputToolbarView; } +#pragma mark - RoomDataSourceDelegate + +- (void)roomDataSource:(RoomDataSource *)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + [self updateInputToolbarEncryptionDecoration]; + [self updateExpandedHeaderEncryptionDecoration]; + [self updateTitleViewEncryptionDecoration]; +} + #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender @@ -3901,7 +4023,12 @@ // We promote here join by room alias instead of room id when an alias is available. NSString *roomIdOrAlias = roomPreviewData.roomId; - if (roomPreviewData.roomAliases.count) + + if (roomPreviewData.roomCanonicalAlias.length) + { + roomIdOrAlias = roomPreviewData.roomCanonicalAlias; + } + else if (roomPreviewData.roomAliases.count) { roomIdOrAlias = roomPreviewData.roomAliases.firstObject; } @@ -5303,8 +5430,36 @@ // Copy action + BOOL isCopyActionEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + + if (isCopyActionEnabled) + { + switch (event.eventType) { + case MXEventTypeRoomMessage: + { + NSString *messageType = event.content[@"msgtype"]; + + if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + isCopyActionEnabled = NO; + } + break; + } + case MXEventTypeKeyVerificationStart: + case MXEventTypeKeyVerificationAccept: + case MXEventTypeKeyVerificationKey: + case MXEventTypeKeyVerificationMac: + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + isCopyActionEnabled = NO; + break; + default: + break; + } + } + RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; - copyMenuItem.isEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + copyMenuItem.isEnabled = isCopyActionEnabled; copyMenuItem.action = ^{ MXStrongifyAndReturnIfNil(self); diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.swift new file mode 100644 index 000000000..158df123c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.swift @@ -0,0 +1,91 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import Reusable + +@objcMembers +final class BubbleCellContentView: UIView, NibLoadable { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet weak var bubbleInfoContainer: UIView! + @IBOutlet weak var bubbleInfoContainerTopConstraint: NSLayoutConstraint! + + @IBOutlet weak var innerContentView: UIView! + + @IBOutlet weak var readReceiptsContainerView: UIView! + @IBOutlet weak var readReceiptsContentView: UIView! + + @IBOutlet weak var bubbleOverlayContainer: UIView! + + @IBOutlet weak var paginationTitleContainerView: UIView! + @IBOutlet weak var paginationLabel: UILabel! + @IBOutlet weak var paginationSeparatorView: UIView! + + // MARK: Private + + private var showReadReceipts: Bool { + get { + return !self.readReceiptsContainerView.isHidden + } + set { + self.readReceiptsContainerView.isHidden = !newValue + } + } + + // MARK: Public + + var showPaginationTitle: Bool { + get { + return !self.paginationTitleContainerView.isHidden + } + set { + self.paginationTitleContainerView.isHidden = !newValue + } + } + + // MARK: - Setup + + class func instantiate() -> BubbleCellContentView { + return BubbleCellContentView.loadFromNib() + } + + // MARK: - Public + + func update(theme: Theme) { + self.backgroundColor = theme.backgroundColor + self.paginationLabel.textColor = theme.tintColor + self.paginationSeparatorView.backgroundColor = theme.tintColor + } +} + +// MARK: - BubbleCellReadReceiptsDisplayable +extension BubbleCellContentView: BubbleCellReadReceiptsDisplayable { + + func addReadReceiptsView(_ readReceiptsView: UIView) { + self.readReceiptsContentView.vc_removeAllSubviews() + self.readReceiptsContentView.vc_addSubViewMatchingParent(readReceiptsView) + self.showReadReceipts = true + } + + func removeReadReceiptsView() { + self.showReadReceipts = false + self.readReceiptsContentView.vc_removeAllSubviews() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib new file mode 100644 index 000000000..774a36c25 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift new file mode 100644 index 000000000..e9b657a03 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift @@ -0,0 +1,22 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objc protocol BubbleCellReadReceiptsDisplayable { + func addReadReceiptsView(_ readReceiptsView: UIView) + func removeReadReceiptsView() +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.h index 4db36a8bf..a97402465 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.h +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.h @@ -29,12 +29,11 @@ extern NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon; @interface RoomEncryptedDataBubbleCell : NSObject /** - Return the icon displayed in front an event in an encrypted room . + Return the icon displayed in front of an event in an encrypted room if needed. - @param event the event. - @param session the matrix session + @param event the bubble component. */ -+ (UIImage*)encryptionIconForEvent:(MXEvent*)event andSession:(MXSession*)session; ++ (UIImage*)encryptionIconForBubbleComponent:(MXKRoomBubbleComponent *)bubbleComponent; /** Set the encryption status icon in front of each bubble component. diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m index ab4c455e5..6958e3ed5 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -21,62 +21,14 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt @implementation RoomEncryptedDataBubbleCell -+ (UIImage*)encryptionIconForEvent:(MXEvent*)event andSession:(MXSession*)session ++ (UIImage*)encryptionIconForBubbleComponent:(MXKRoomBubbleComponent *)bubbleComponent { - NSString *encryptionIcon; - - if (!event.isEncrypted) + if (!bubbleComponent.showEncryptionBadge) { - encryptionIcon = @"e2e_unencrypted"; - - if (event.isLocalEvent - || event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event - { - // Patch: Display the verified icon by default on pending outgoing messages in the encrypted rooms when the encryption is enabled - MXRoom *room = [session roomWithRoomId:event.roomId]; - if (room.summary.isEncrypted && session.crypto) - { - // The outgoing message are encrypted by default - encryptionIcon = @"e2e_verified"; - } - } - } - else if (event.decryptionError) - { - encryptionIcon = @"e2e_blocked"; - } - else - { - MXDeviceInfo *deviceInfo = [session.crypto eventDeviceInfo:event]; - - if (deviceInfo) - { - switch (deviceInfo.verified) - { - case MXDeviceUnknown: - case MXDeviceUnverified: - { - encryptionIcon = @"e2e_warning"; - break; - } - case MXDeviceVerified: - { - encryptionIcon = @"e2e_verified"; - break; - } - default: - break; - } - } + return nil; } - if (!encryptionIcon) - { - // Use the warning icon by default - encryptionIcon = @"e2e_warning"; - } - - return [UIImage imageNamed:encryptionIcon]; + return [UIImage imageNamed:@"encryption_warning"]; } + (void)addEncryptionStatusFromBubbleData:(MXKRoomBubbleCellData *)bubbleData inContainerView:(UIView *)containerView @@ -102,21 +54,25 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt { continue; } - - UIImage *icon = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; - UIImageView *encryptStatusImageView = [[UIImageView alloc] initWithImage:icon]; - CGRect frame = encryptStatusImageView.frame; - frame.origin.y = component.position.y + 3; - encryptStatusImageView.frame = frame; + UIImage *icon = [[self class] encryptionIconForBubbleComponent:component]; - CGPoint center = encryptStatusImageView.center; - center.x = containerView.frame.size.width / 2; - encryptStatusImageView.center = center; - - encryptStatusImageView.tag = componentIndex; - - [containerView addSubview:encryptStatusImageView]; + if (icon) + { + UIImageView *encryptStatusImageView = [[UIImageView alloc] initWithImage:icon]; + + CGRect frame = encryptStatusImageView.frame; + frame.origin.y = component.position.y + 3; + encryptStatusImageView.frame = frame; + + CGPoint center = encryptStatusImageView.center; + center.x = containerView.frame.size.width / 2; + encryptStatusImageView.center = center; + + encryptStatusImageView.tag = componentIndex; + + [containerView addSubview:encryptStatusImageView]; + } } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.m index e46cba929..3139b418c 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.m @@ -43,7 +43,7 @@ MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; if (component) { - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m index af76485f5..b0e2b5e76 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m @@ -45,7 +45,7 @@ MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; if (component) { - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m index a127189ee..b8be790cb 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m @@ -43,7 +43,7 @@ MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; if (component) { - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.m index 6b0e0c6de..c5dfe21a1 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.m @@ -43,7 +43,7 @@ MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; if (component) { - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } } } @@ -63,4 +63,4 @@ } } -@end \ No newline at end of file +@end diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m index 9def7d21f..ac6afe709 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m @@ -43,7 +43,7 @@ MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; if (component) { - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } } } @@ -62,4 +62,5 @@ } } } -@end \ No newline at end of file + +@end diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m index 28d317cf4..41aec08ad 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m @@ -43,7 +43,7 @@ MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; if (component) { - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } } } @@ -62,4 +62,4 @@ } } } -@end \ No newline at end of file +@end diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift new file mode 100644 index 000000000..0344b20b9 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift @@ -0,0 +1,261 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objcMembers +class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { + + // MARK: - Constants + + private enum Sizing { + static var sizes = Set() + } + + // MARK: - Properties + + // MARK: Public + + weak var keyVerificationCellInnerContentView: KeyVerificationCellInnerContentView? + + weak var bubbleCellContentView: BubbleCellContentView? + + override var bubbleInfoContainer: UIView! { + get { + guard let infoContainer = self.bubbleCellContentView?.bubbleInfoContainer else { + fatalError("[KeyVerificationBaseBubbleCell] bubbleInfoContainer should not be used before set") + } + return infoContainer + } + set { + super.bubbleInfoContainer = newValue + } + } + + override var bubbleOverlayContainer: UIView! { + get { + guard let overlayContainer = self.bubbleCellContentView?.bubbleOverlayContainer else { + fatalError("[KeyVerificationBaseBubbleCell] bubbleOverlayContainer should not be used before set") + } + return overlayContainer + } + set { + super.bubbleInfoContainer = newValue + } + } + + override var bubbleInfoContainerTopConstraint: NSLayoutConstraint! { + get { + guard let infoContainerTopConstraint = self.bubbleCellContentView?.bubbleInfoContainerTopConstraint else { + fatalError("[KeyVerificationBaseBubbleCell] bubbleInfoContainerTopConstraint should not be used before set") + } + return infoContainerTopConstraint + } + set { + super.bubbleInfoContainerTopConstraint = newValue + } + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + + self.selectionStyle = .none + self.setupContentView() + self.update(theme: ThemeService.shared().theme) + + super.setupViews() + } + + // MARK: - Public + + func update(theme: Theme) { + self.bubbleCellContentView?.update(theme: theme) + self.keyVerificationCellInnerContentView?.update(theme: theme) + } + + func buildUserInfoText(with userId: String, userDisplayName: String?) -> String { + + let userInfoText: String + + if let userDisplayName = userDisplayName { + userInfoText = "\(userId) (\(userDisplayName))" + } else { + userInfoText = userId + } + + return userInfoText + } + + func senderId(from bubbleCellData: MXKRoomBubbleCellData) -> String { + return bubbleCellData.senderId ?? "" + } + + func senderDisplayName(from bubbleCellData: MXKRoomBubbleCellData) -> String? { + let senderId = self.senderId(from: bubbleCellData) + guard let senderDisplayName = bubbleCellData.senderDisplayName, senderId != senderDisplayName else { + return nil + } + return senderDisplayName + } + + class func sizingView() -> KeyVerificationBaseBubbleCell { + fatalError("[KeyVerificationBaseBubbleCell] Subclass should implement this method") + } + + class func sizingViewHeightHashValue(from bubbleCellData: MXKRoomBubbleCellData) -> Int { + + var hasher = Hasher() + + let sizingView = self.sizingView() + sizingView.render(bubbleCellData) + + // Add cell class name + hasher.combine(self.defaultReuseIdentifier()) + + if let keyVerificationCellInnerContentView = sizingView.keyVerificationCellInnerContentView { + + // Add other user info + if let otherUserInfo = keyVerificationCellInnerContentView.otherUserInfo { + hasher.combine(otherUserInfo) + } + + // Add request status text + if keyVerificationCellInnerContentView.isRequestStatusHidden == false, + let requestStatusText = sizingView.keyVerificationCellInnerContentView?.requestStatusText { + hasher.combine(requestStatusText) + } + } + + return hasher.finalize() + } + + // MARK: - Overrides + + override class func defaultReuseIdentifier() -> String! { + return String(describing: self) + } + + override func didEndDisplay() { + super.didEndDisplay() + self.removeReadReceiptsView() + } + + override class func height(for cellData: MXKCellData!, withMaximumWidth maxWidth: CGFloat) -> CGFloat { + guard let cellData = cellData else { + return 0 + } + + guard let roomBubbleCellData = cellData as? MXKRoomBubbleCellData else { + return 0 + } + + let height: CGFloat + + let sizingViewHeight = self.findOrCreateSizingViewHeight(from: roomBubbleCellData) + + if let cachedHeight = sizingViewHeight.heights[maxWidth] { + height = cachedHeight + } else { + height = self.contentViewHeight(for: roomBubbleCellData, fitting: maxWidth) + sizingViewHeight.heights[maxWidth] = height + } + + return height + } + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + if let bubbleData = self.bubbleData, + let bubbleCellContentView = self.bubbleCellContentView, + let paginationDate = bubbleData.date, + bubbleCellContentView.showPaginationTitle { + bubbleCellContentView.paginationLabel.text = bubbleData.eventFormatter.dateString(from: paginationDate, withTime: false)?.uppercased() + } + } + + // MARK: - Private + + private func setupContentView() { + if self.bubbleCellContentView == nil { + + let bubbleCellContentView = BubbleCellContentView.instantiate() + + let innerContentView = KeyVerificationCellInnerContentView.instantiate() + + bubbleCellContentView.innerContentView.vc_addSubViewMatchingParent(innerContentView) + + self.contentView.vc_addSubViewMatchingParent(bubbleCellContentView) + + self.bubbleCellContentView = bubbleCellContentView + self.keyVerificationCellInnerContentView = innerContentView + } + } + + private static func findOrCreateSizingViewHeight(from bubbleData: MXKRoomBubbleCellData) -> SizingViewHeight { + + let sizingViewHeight: SizingViewHeight + let bubbleDataHashValue = bubbleData.hashValue + + if let foundSizingViewHeight = self.Sizing.sizes.first(where: { (sizingViewHeight) -> Bool in + return sizingViewHeight.uniqueIdentifier == bubbleDataHashValue + }) { + sizingViewHeight = foundSizingViewHeight + } else { + sizingViewHeight = SizingViewHeight(uniqueIdentifier: bubbleDataHashValue) + } + + return sizingViewHeight + } + + private static func contentViewHeight(for cellData: MXKCellData, fitting width: CGFloat) -> CGFloat { + let sizingView = self.sizingView() + + sizingView.render(cellData) + sizingView.layoutIfNeeded() + + let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height) + var height = sizingView.systemLayoutSizeFitting(fittingSize).height + + if let roomBubbleCellData = cellData as? RoomBubbleCellData, let readReceipts = roomBubbleCellData.readReceipts, readReceipts.count > 0 { + height+=RoomBubbleCellLayout.readReceiptsViewHeight + } + + return height + } +} + +// MARK: - BubbleCellReadReceiptsDisplayable +extension KeyVerificationBaseBubbleCell: BubbleCellReadReceiptsDisplayable { + + func addReadReceiptsView(_ readReceiptsView: UIView) { + self.bubbleCellContentView?.addReadReceiptsView(readReceiptsView) + } + + func removeReadReceiptsView() { + self.bubbleCellContentView?.removeReadReceiptsView() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift new file mode 100644 index 000000000..1402be19f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift @@ -0,0 +1,180 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import Reusable + +final class KeyVerificationCellInnerContentView: UIView, NibLoadable { + + // MARK: - Constants + + private enum Constants { + static let cornerRadius: CGFloat = 8.0 + static let buttonBackgroundColorAlpha: CGFloat = 0.2 + static let buttonCornerRadius: CGFloat = 6.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var badgeImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + @IBOutlet private weak var otherUserInformationLabel: UILabel! + + @IBOutlet private weak var requestStatusLabel: UILabel! + + @IBOutlet private weak var buttonsContainerView: UIView! + @IBOutlet private weak var acceptButton: UIButton! + @IBOutlet private weak var declineButton: UIButton! + + // MARK: Public + + var isButtonsHidden: Bool { + get { + return self.acceptButton.isHidden && self.declineButton.isHidden + } + set { + self.buttonsContainerView.isHidden = newValue + } + } + + var isRequestStatusHidden: Bool { + get { + return self.requestStatusLabel.isHidden + } + set { + self.requestStatusLabel.isHidden = newValue + } + } + + var badgeImage: UIImage? { + get { + return self.badgeImageView.image + } + set { + self.badgeImageView.image = newValue + } + } + + var title: String? { + get { + return self.titleLabel.text + } + set { + self.titleLabel.text = newValue + } + } + + var otherUserInfo: String? { + return self.otherUserInformationLabel.text + } + + var requestStatusText: String? { + get { + return self.requestStatusLabel.text + } + set { + self.requestStatusLabel.text = newValue + } + } + + var acceptActionHandler: (() -> Void)? + + var declineActionHandler: (() -> Void)? + + // MARK: - Setup + + static func instantiate() -> KeyVerificationCellInnerContentView { + let view = KeyVerificationCellInnerContentView.loadFromNib() + return view + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.layer.masksToBounds = true + self.acceptButton.layer.masksToBounds = true + + self.acceptButton.titleLabel?.adjustsFontSizeToFitWidth = true + self.acceptButton.titleLabel?.minimumScaleFactor = 0.5 + self.acceptButton.titleLabel?.baselineAdjustment = .alignCenters + + self.acceptButton.setTitle(VectorL10n.keyVerificationTileRequestIncomingApprovalAccept, for: .normal) + + self.declineButton.layer.masksToBounds = true + + self.declineButton.titleLabel?.adjustsFontSizeToFitWidth = true + self.declineButton.titleLabel?.minimumScaleFactor = 0.5 + self.declineButton.titleLabel?.baselineAdjustment = .alignCenters + + self.declineButton.setTitle(VectorL10n.keyVerificationTileRequestIncomingApprovalDecline, for: .normal) + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layer.cornerRadius = Constants.cornerRadius + + if self.isButtonsHidden == false { + self.acceptButton.layer.cornerRadius = Constants.buttonCornerRadius + self.declineButton.layer.cornerRadius = Constants.buttonCornerRadius + } + } + + // MARK: - Public + + func update(theme: Theme) { + self.backgroundColor = theme.headerBackgroundColor + self.titleLabel.textColor = theme.textPrimaryColor + self.otherUserInformationLabel.textColor = theme.textSecondaryColor + + self.acceptButton.vc_setBackgroundColor(theme.tintColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + self.declineButton.vc_setBackgroundColor(theme.noticeColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + } + + func updateSenderInfo(with userId: String, userDisplayName: String?) { + self.otherUserInformationLabel.text = self.buildUserInfoText(with: userId, userDisplayName: userDisplayName) + } + + // MARK: - Private + + private func buildUserInfoText(with userId: String, userDisplayName: String?) -> String { + + let userInfoText: String + + if let userDisplayName = userDisplayName { + userInfoText = "\(userId) (\(userDisplayName))" + } else { + userInfoText = userId + } + + return userInfoText + } + + // MARK: - Action + + @IBAction private func declineButtonAction(_ sender: Any) { + self.declineActionHandler?() + } + + @IBAction private func acceptButtonAction(_ sender: Any) { + self.acceptActionHandler?() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib new file mode 100644 index 000000000..404d95945 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift new file mode 100644 index 000000000..70353531f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift @@ -0,0 +1,102 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationConclusionBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + self.keyVerificationCellInnerContentView?.isButtonsHidden = true + self.keyVerificationCellInnerContentView?.isRequestStatusHidden = true + } + + // MARK: - Overrides + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, + let bubbleData = self.bubbleData as? RoomBubbleCellData, + let viewData = self.viewData(from: bubbleData) else { + NSLog("[KeyVerificationConclusionBubbleCell] Fail to render \(String(describing: cellData))") + return + } + + keyVerificationCellInnerContentView.badgeImage = viewData.badgeImage + keyVerificationCellInnerContentView.title = viewData.title + keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + } + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } + + // MARK: - Private + + private func viewData(from roomBubbleData: RoomBubbleCellData) -> KeyVerificationConclusionViewData? { + guard let event = roomBubbleData.bubbleComponents.first?.event else { + return nil + } + + let viewData: KeyVerificationConclusionViewData? + + let senderId = self.senderId(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) + let title: String? + let badgeImage: UIImage? + + switch event.eventType { + case .keyVerificationDone: + badgeImage = Asset.Images.encryptionTrusted.image + title = VectorL10n.keyVerificationTileConclusionDoneTitle + case .keyVerificationCancel: + badgeImage = Asset.Images.encryptionWarning.image + title = VectorL10n.keyVerificationTileConclusionWarningTitle + default: + badgeImage = nil + title = nil + } + + if let title = title, let badgeImage = badgeImage { + viewData = KeyVerificationConclusionViewData(badgeImage: badgeImage, + title: title, + senderId: senderId, + senderDisplayName: senderDisplayName) + } else { + viewData = nil + } + + return viewData + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift new file mode 100644 index 000000000..4df053de6 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift @@ -0,0 +1,24 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationConclusionViewData { + let badgeImage: UIImage + let title: String + let senderId: String + let senderDisplayName: String? +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..7cc1b8277 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift @@ -0,0 +1,52 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +final class KeyVerificationConclusionWithPaginationTitleBubbleCell: KeyVerificationConclusionBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationConclusionWithPaginationTitleBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let bubbleCellContentView = self.bubbleCellContentView else { + fatalError("[KeyVerificationConclusionWithPaginationTitleBubbleCell] bubbleCellContentView should not be nil") + } + + bubbleCellContentView.showPaginationTitle = true + } + + // MARK: - Overrides + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift new file mode 100644 index 000000000..3093b5206 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift @@ -0,0 +1,104 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +class KeyVerificationIncomingRequestApprovalBubbleCell: KeyVerificationBaseBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationIncomingRequestApprovalBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView else { + fatalError("[KeyVerificationIncomingRequestApprovalBubbleCell] keyVerificationCellInnerContentView should not be nil") + } + + keyVerificationCellInnerContentView.isButtonsHidden = false + keyVerificationCellInnerContentView.isRequestStatusHidden = true + keyVerificationCellInnerContentView.badgeImage = Asset.Images.encryptionNormal.image + } + + // MARK: - Overrides + + override func prepareForReuse() { + super.prepareForReuse() + + self.keyVerificationCellInnerContentView?.acceptActionHandler = nil + self.keyVerificationCellInnerContentView?.declineActionHandler = nil + } + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, + let bubbleData = self.bubbleData, + let viewData = self.viewData(from: bubbleData) else { + NSLog("[KeyVerificationIncomingRequestApprovalBubbleCell] Fail to render \(String(describing: cellData))") + return + } + + keyVerificationCellInnerContentView.title = viewData.title + keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + + let actionUserInfo: [AnyHashable: Any]? + + if let eventId = bubbleData.getFirstBubbleComponentWithDisplay()?.event.eventId { + actionUserInfo = [kMXKRoomBubbleCellEventIdKey: eventId] + } else { + actionUserInfo = nil + } + + keyVerificationCellInnerContentView.acceptActionHandler = { [weak self] in + self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed, userInfo: actionUserInfo) + } + + keyVerificationCellInnerContentView.declineActionHandler = { [weak self] in + self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed, userInfo: actionUserInfo) + } + } + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } + + // MARK: - Private + + private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationIncomingRequestApprovalViewData? { + + let senderId = self.senderId(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) + let title = VectorL10n.keyVerificationTileRequestIncomingTitle + + return KeyVerificationIncomingRequestApprovalViewData(title: title, + senderId: senderId, + senderDisplayName: senderDisplayName) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift new file mode 100644 index 000000000..91824fe16 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift @@ -0,0 +1,23 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationIncomingRequestApprovalViewData { + let title: String + let senderId: String + let senderDisplayName: String? +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..6b4b529f1 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift @@ -0,0 +1,52 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +final class KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell: KeyVerificationIncomingRequestApprovalBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let bubbleCellContentView = self.bubbleCellContentView else { + fatalError("[KeyVerificationRequestStatusWithPaginationTitleBubbleCell] bubbleCellContentView should not be nil") + } + + bubbleCellContentView.showPaginationTitle = true + } + + // MARK: - Overrides + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift new file mode 100644 index 000000000..b5af25a5a --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift @@ -0,0 +1,125 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +class KeyVerificationRequestStatusBubbleCell: KeyVerificationBaseBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationRequestStatusBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView else { + fatalError("[KeyVerificationRequestStatusBubbleCell] keyVerificationCellInnerContentView should not be nil") + } + + keyVerificationCellInnerContentView.isButtonsHidden = true + keyVerificationCellInnerContentView.isRequestStatusHidden = false + keyVerificationCellInnerContentView.badgeImage = Asset.Images.encryptionNormal.image + } + + // MARK: - Overrides + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, + let roomBubbleCellData = self.bubbleData as? RoomBubbleCellData, + let viewData = self.viewData(from: roomBubbleCellData) else { + NSLog("[KeyVerificationRequestStatusBubbleCell] Fail to render \(String(describing: cellData))") + return + } + + keyVerificationCellInnerContentView.title = viewData.title + keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + keyVerificationCellInnerContentView.requestStatusText = viewData.statusText + } + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } + + // MARK: - Private + + private func viewData(from roomBubbleCellData: RoomBubbleCellData) -> KeyVerificationRequestStatusViewData? { + + let senderId = self.senderId(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) + let title: String + let statusText: String? + + if roomBubbleCellData.isIncoming { + title = VectorL10n.keyVerificationTileRequestIncomingTitle + } else { + title = VectorL10n.keyVerificationTileRequestOutgoingTitle + } + + if let keyVerification = roomBubbleCellData.keyVerification { + switch keyVerification.state { + case .requestPending: + if !roomBubbleCellData.isIncoming { + statusText = VectorL10n.keyVerificationTileRequestStatusWaiting + } else { + if roomBubbleCellData.isKeyVerificationOperationPending { + statusText = VectorL10n.keyVerificationTileRequestStatusDataLoading + } else { + // Should not happen, KeyVerificationIncomingRequestApprovalBubbleCell should be displayed in this case. + statusText = nil + } + } + case .requestExpired: + statusText = VectorL10n.keyVerificationTileRequestStatusExpired + case .requestCancelled, .transactionCancelled: + let userName = senderDisplayName ?? senderId + statusText = VectorL10n.keyVerificationTileRequestStatusCancelled(userName) + case .requestCancelledByMe, .transactionCancelledByMe: + statusText = VectorL10n.keyVerificationTileRequestStatusCancelledByMe + default: + statusText = VectorL10n.keyVerificationTileRequestStatusAccepted + } + } else { + statusText = VectorL10n.keyVerificationTileRequestStatusDataLoading + } + + let viewData: KeyVerificationRequestStatusViewData? + + if let statusText = statusText { + viewData = KeyVerificationRequestStatusViewData(title: title, + senderId: senderId, + senderDisplayName: senderDisplayName, + statusText: statusText) + } else { + viewData = nil + } + + return viewData + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift new file mode 100644 index 000000000..c411b56ca --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift @@ -0,0 +1,24 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationRequestStatusViewData { + let title: String + let senderId: String + let senderDisplayName: String? + let statusText: String +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..f9c0e6465 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift @@ -0,0 +1,52 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +@objcMembers +final class KeyVerificationRequestStatusWithPaginationTitleBubbleCell: KeyVerificationRequestStatusBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationRequestStatusWithPaginationTitleBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let bubbleCellContentView = self.bubbleCellContentView else { + fatalError("[KeyVerificationRequestStatusWithPaginationTitleBubbleCell] bubbleCellContentView should not be nil") + } + + bubbleCellContentView.showPaginationTitle = true + } + + // MARK: - Overrides + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift new file mode 100644 index 000000000..f1607fb4f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift @@ -0,0 +1,43 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class SizingViewHeight: Hashable, Equatable { + + // MARK: - Properties + + let uniqueIdentifier: Int + var heights: [CGFloat /* width */: CGFloat /* height */] = [:] + + // MARK: - Setup + + init(uniqueIdentifier: Int) { + self.uniqueIdentifier = uniqueIdentifier + } + + // MARK: - Hashable + + func hash(into hasher: inout Hasher) { + hasher.combine(self.uniqueIdentifier) + } + + // MARK: - Equatable + + static func == (lhs: SizingViewHeight, rhs: SizingViewHeight) -> Bool { + return lhs.uniqueIdentifier == rhs.uniqueIdentifier + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m index f63b5180c..b1e6d9477 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m @@ -163,7 +163,7 @@ { // Set the right device info icon self.encryptionStatusView.hidden = NO; - self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForBubbleComponent:component]; } else { diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 9cc1110f3..41d4c0aff 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -65,7 +65,7 @@ typedef enum : NSUInteger `RoomInputToolbarView` instance is a view used to handle all kinds of available inputs for a room (message composer, attachments selection...). */ -@interface RoomInputToolbarView : MXKRoomInputToolbarViewWithHPGrowingText +@interface RoomInputToolbarView : MXKRoomInputToolbarViewWithHPGrowingText /** The delegate notified when inputs are ready. diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 855c21454..233ccab1d 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -128,8 +128,6 @@ if (_isEncryptionEnabled) { - self.encryptedRoomIcon.image = [UIImage imageNamed:@"e2e_verified"]; - // Check the device screen size before using large placeholder if ([GBDeviceInfo deviceInfo].family == GBDeviceFamilyiPad || [GBDeviceInfo deviceInfo].displayInfo.display >= GBDeviceDisplay4p7Inch) { @@ -138,8 +136,6 @@ } else { - self.encryptedRoomIcon.image = [UIImage imageNamed:@"e2e_unencrypted"]; - // Check the device screen size before using large placeholder if ([GBDeviceInfo deviceInfo].family == GBDeviceFamilyiPad || [GBDeviceInfo deviceInfo].displayInfo.display >= GBDeviceDisplay4p7Inch) { diff --git a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h index ff6a1b40d..e22e0422c 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h @@ -19,6 +19,7 @@ @interface ExpandedRoomTitleView : RoomTitleView @property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; +@property (weak, nonatomic) IBOutlet UIImageView *roomAvatarBadgeImageView; @property (weak, nonatomic) IBOutlet UIView *roomAvatarHeaderBackground; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomAvatarHeaderBackgroundHeightConstraint; diff --git a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib index 353189cdd..453addbd9 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib @@ -1,11 +1,11 @@ - + - + @@ -27,11 +27,20 @@ + + + + + + + + + @@ -148,6 +157,7 @@ + @@ -155,10 +165,11 @@ + - - + + diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.h b/Riot/Modules/Room/Views/Title/RoomTitleView.h index 23b198f5e..52fe01acf 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.h +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.h @@ -39,6 +39,7 @@ @property (weak, nonatomic) IBOutlet UIView *addParticipantMask; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *displayNameCenterXConstraint; @property (weak, nonatomic) IBOutlet UIImageView *roomDetailsIconImageView; +@property (weak, nonatomic) IBOutlet UIImageView *badgeImageView; /** The room preview data may be used when mxRoom instance is not available diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.m b/Riot/Modules/Room/Views/Title/RoomTitleView.m index 898103702..8e7329564 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.m @@ -37,6 +37,8 @@ { [super awakeFromNib]; + self.badgeImageView.image = nil; + if (_titleMask) { UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)]; diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.xib b/Riot/Modules/Room/Views/Title/RoomTitleView.xib index 92c0c947c..6b4b0722d 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.xib @@ -1,11 +1,11 @@ - - + + - + @@ -15,8 +15,16 @@ + + + + + + + + - + @@ -26,7 +34,7 @@ - + @@ -34,11 +42,11 @@ - + - + @@ -55,14 +63,18 @@ + + + + @@ -70,9 +82,11 @@ + - + + diff --git a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m index b73d7c2fe..12e04b1c6 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -19,11 +19,11 @@ #import "AppDelegate.h" #import "Riot-Swift.h" -@interface RoomKeyRequestViewController () +@interface RoomKeyRequestViewController () { void (^onComplete)(void); - DeviceVerificationCoordinatorBridgePresenter *deviceVerificationCoordinatorBridgePresenter; + KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; BOOL wasNewDevice; } @@ -136,30 +136,40 @@ UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; if (rootViewController) { - deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:_mxSession]; - deviceVerificationCoordinatorBridgePresenter.delegate = self; + keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:_mxSession]; + keyVerificationCoordinatorBridgePresenter.delegate = self; - [deviceVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:_device.userId otherDeviceId:_device.deviceId animated:YES]; + [keyVerificationCoordinatorBridgePresenter presentFrom:rootViewController otherUserId:_device.userId otherDeviceId:_device.deviceId animated:YES]; } } #pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate -- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { - [deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; - deviceVerificationCoordinatorBridgePresenter = nil; + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ + [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + keyVerificationCoordinatorBridgePresenter = nil; + // Check device new status - [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { - + [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { + MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:self.device.deviceId forUser:self.device.userId]; - if (deviceInfo && deviceInfo.verified == MXDeviceVerified) + if (deviceInfo && deviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) { // Accept the received requests from this device // As the device is now verified, all other key requests will be automatically accepted. [self.mxSession.crypto acceptAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{ - + onComplete(); }]; } @@ -169,7 +179,7 @@ [self show]; } } failure:^(NSError *error) { - + // Should not happen (the device is in the crypto db) [self show]; }]; diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m index 975146130..00b8d065d 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m @@ -84,17 +84,18 @@ static CGFloat const kTextFontSize = 15.0; // Do any additional setup after loading the view. self.title = NSLocalizedStringFromTable(@"deactivate_account_title", @"Vector", nil); + + [self setupViews]; self.errorPresentation = [[MXKErrorAlertPresentation alloc] init]; - [self setupStringAttributes]; - [self setupViews]; - [self userInterfaceThemeDidChange]; [self registerThemeNotification]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + + [self userInterfaceThemeDidChange]; // Screen tracking [[Analytics sharedInstance] trackScreen:@"DeactivateAccount"]; @@ -126,9 +127,17 @@ static CGFloat const kTextFontSize = 15.0; [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; + + self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; + + [self updateStringAttributes]; + [self updateNavigationBar]; + [self updateDeactivateAcccountButton]; + [self updateDeactivateAccountInfosLabel]; + [self updateForgetMessagesInfoLabel]; } -- (void)setupStringAttributes +- (void)updateStringAttributes { self.normalStringAttributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:kTextFontSize], @@ -144,34 +153,31 @@ static CGFloat const kTextFontSize = 15.0; - (void)setupViews { - [self setupNavigationBar]; - [self setupDeactivateAcccountButton]; - [self setupDeactivateAccountInfosLabel]; - [self setupForgetMessagesInfoLabel]; -} - -- (void)setupNavigationBar -{ - self.navigationController.navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName: ThemeService.shared.theme.warningColor }; - + // Cancel bar button UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) style:UIBarButtonItemStylePlain target:self action:@selector(cancelButtonAction:)]; self.navigationItem.rightBarButtonItem = cancelBarButtonItem; -} -- (void)setupDeactivateAcccountButton -{ + // Deactivate button // Adjust button font size for small devices self.deactivateAcccountButton.titleLabel.adjustsFontSizeToFitWidth = YES; self.deactivateAcccountButton.titleLabel.minimumScaleFactor = 0.5; self.deactivateAcccountButton.titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; - self.deactivateAcccountButton.layer.masksToBounds = YES; + [self.deactivateAcccountButton setTitle:NSLocalizedStringFromTable(@"deactivate_account_validate_action", @"Vector", nil) forState:UIControlStateNormal]; +} + +- (void)updateNavigationBar +{ + self.navigationController.navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName: ThemeService.shared.theme.warningColor }; +} + +- (void)updateDeactivateAcccountButton +{ self.deactivateAcccountButton.backgroundColor = ThemeService.shared.theme.tintColor; - [self.deactivateAcccountButton setTitle:NSLocalizedStringFromTable(@"deactivate_account_validate_action", @"Vector", nil) forState:UIControlStateNormal]; [self.deactivateAcccountButton setTitleColor:ThemeService.shared.theme.headerTextSecondaryColor forState:UIControlStateDisabled]; } -- (void)setupDeactivateAccountInfosLabel +- (void)updateDeactivateAccountInfosLabel { NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; @@ -188,7 +194,7 @@ static CGFloat const kTextFontSize = 15.0; [self.deactivateAccountInfosLabel setAttributedText:attributedString]; } -- (void)setupForgetMessagesInfoLabel +- (void)updateForgetMessagesInfoLabel { NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard index 687d1ef05..13f41d437 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard @@ -1,12 +1,11 @@ - + - - + @@ -57,7 +56,7 @@ Message visibility in Matrix is similar to email. Our forgetting your messages m