Merge branch 'develop' into fix_join_room_no_guest_access

This commit is contained in:
Chris Moos 2020-02-06 11:34:10 -07:00 committed by GitHub
commit a4a810e393
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
141 changed files with 8907 additions and 1344 deletions

View file

@ -1,9 +1,19 @@
Changes in 0.10.5 (TBD)
Changes in 0.11.1 (TBD)
===============================================
Bug fix:
* Fix error when joining some public rooms. (#2888).
Changes in 0.11.0 (2020-xx-xx)
===============================================
Improvements:
* E2E: Do not warn anymore for unknown devices
* ON/OFF Cross-signing development in a Lab setting (#2855).
* 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).
Changes in 0.10.4 (2019-12-11)
===============================================

View file

@ -78,6 +78,8 @@
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 */; };
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 +100,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 */; };
@ -129,6 +133,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 +179,26 @@
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 */; };
B12D7A0423E43DCC00FACEDC /* KeyVerificationKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D7A0323E43DCC00FACEDC /* KeyVerificationKind.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 */; };
@ -517,6 +541,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 +576,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 */; };
@ -730,6 +780,9 @@
32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifiedViewController.swift; sourceTree = "<group>"; };
32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewController.swift; sourceTree = "<group>"; };
32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationDataLoadingViewController.storyboard; sourceTree = "<group>"; };
3291DC8923E0BE820009732F /* Security.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Security.storyboard; sourceTree = "<group>"; };
3291DC8B23E0BFF10009732F /* SecurityViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecurityViewController.h; sourceTree = "<group>"; };
3291DC8C23E0BFF10009732F /* SecurityViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecurityViewController.m; sourceTree = "<group>"; };
329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionActionViewCell.xib; sourceTree = "<group>"; };
329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionActionViewCell.swift; sourceTree = "<group>"; };
32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewState.swift; sourceTree = "<group>"; };
@ -756,6 +809,9 @@
32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupViewState.swift; sourceTree = "<group>"; };
32BF995421FA2AB700698084 /* SettingsKeyBackupViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupViewAction.swift; sourceTree = "<group>"; };
32BF995621FB07A400698084 /* SettingsKeyBackupTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupTableViewSection.swift; sourceTree = "<group>"; };
32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ManageSessionViewController.m; sourceTree = "<group>"; };
32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ManageSession.storyboard; sourceTree = "<group>"; };
32D5D15F23E1EE2700E3E37C /* ManageSessionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ManageSessionViewController.h; sourceTree = "<group>"; };
32D7159E2146CC6F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Vector.strings; sourceTree = "<group>"; };
32D7159F2146CC7F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
32D715A02146CC8800DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@ -806,6 +862,12 @@
B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromPassphraseViewController.storyboard; sourceTree = "<group>"; };
B105778C2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupSuccessFromRecoveryKeyViewController.swift; sourceTree = "<group>"; };
B105778E2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard; sourceTree = "<group>"; };
B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalBubbleCell.swift; sourceTree = "<group>"; };
B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalViewData.swift; sourceTree = "<group>"; };
B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusViewData.swift; sourceTree = "<group>"; };
B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionViewData.swift; sourceTree = "<group>"; };
B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleCellReadReceiptsDisplayable.swift; sourceTree = "<group>"; };
B108932923ACBA0B00802670 /* SizingViewHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizingViewHeight.swift; sourceTree = "<group>"; };
B1098BDA21ECE09E000DDA48 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
B1098BDC21ECE09E000DDA48 /* Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = "<group>"; };
B1098BDE21ECE09E000DDA48 /* RiotDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RiotDefaults.swift; sourceTree = "<group>"; };
@ -846,12 +908,26 @@
B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewAction.swift; sourceTree = "<group>"; };
B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewState.swift; sourceTree = "<group>"; };
B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageURLParser.swift; sourceTree = "<group>"; };
B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinator.swift; sourceTree = "<group>"; };
B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationStartViewController.storyboard; sourceTree = "<group>"; };
B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModelType.swift; sourceTree = "<group>"; };
B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewController.swift; sourceTree = "<group>"; };
B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewState.swift; sourceTree = "<group>"; };
B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinatorType.swift; sourceTree = "<group>"; };
B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModel.swift; sourceTree = "<group>"; };
B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewAction.swift; sourceTree = "<group>"; };
B12D7A0323E43DCC00FACEDC /* KeyVerificationKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationKind.swift; sourceTree = "<group>"; };
B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = "<group>"; };
B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = "<group>"; };
B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = "<group>"; };
B139C22021FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewState.swift; sourceTree = "<group>"; };
B139C22221FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseCoordinatorType.swift; sourceTree = "<group>"; };
B139C22421FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseCoordinator.swift; sourceTree = "<group>"; };
B14084C523BF76890010F692 /* BubbleCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleCellContentView.swift; sourceTree = "<group>"; };
B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleCellContentView.xib; sourceTree = "<group>"; };
B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift; sourceTree = "<group>"; };
B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionWithPaginationTitleBubbleCell.swift; sourceTree = "<group>"; };
B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift; sourceTree = "<group>"; };
B140B4A121F87F7100E3F5FE /* OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = "<group>"; };
B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
@ -1379,6 +1455,28 @@
B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewController.swift; sourceTree = "<group>"; };
B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WidgetPermissionViewController.storyboard; sourceTree = "<group>"; };
B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewModel.swift; sourceTree = "<group>"; };
B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinatorType.swift; sourceTree = "<group>"; };
B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinator.swift; sourceTree = "<group>"; };
B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCell.swift; sourceTree = "<group>"; };
B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UserVerificationSessionStatusCell.xib; sourceTree = "<group>"; };
B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewModelType.swift; sourceTree = "<group>"; };
B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewModel.swift; sourceTree = "<group>"; };
B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusCoordinatorType.swift; sourceTree = "<group>"; };
B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewState.swift; sourceTree = "<group>"; };
B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewAction.swift; sourceTree = "<group>"; };
B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewController.swift; sourceTree = "<group>"; };
B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationSessionsStatusViewController.storyboard; sourceTree = "<group>"; };
B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusCoordinator.swift; sourceTree = "<group>"; };
B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserEncryptionTrustLevel.h; sourceTree = "<group>"; };
B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewState.swift; sourceTree = "<group>"; };
B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewModelType.swift; sourceTree = "<group>"; };
B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewModel.swift; sourceTree = "<group>"; };
B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationSessionStatusViewController.storyboard; sourceTree = "<group>"; };
B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCoordinatorType.swift; sourceTree = "<group>"; };
B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewAction.swift; sourceTree = "<group>"; };
B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewController.swift; sourceTree = "<group>"; };
B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCoordinator.swift; sourceTree = "<group>"; };
B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPresenter.swift; sourceTree = "<group>"; };
B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorType.swift; sourceTree = "<group>"; };
B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
@ -1393,6 +1491,11 @@
B1C45A81232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B1C45A82232A8C2600165425 /* SettingsIdentityServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerViewModel.swift; sourceTree = "<group>"; };
B1C45A83232A8C2600165425 /* SettingsIdentityServerViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerViewAction.swift; sourceTree = "<group>"; };
B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationCellInnerContentView.swift; sourceTree = "<group>"; };
B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = KeyVerificationCellInnerContentView.xib; sourceTree = "<group>"; };
B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusBubbleCell.swift; sourceTree = "<group>"; };
B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationBaseBubbleCell.swift; sourceTree = "<group>"; };
B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionBubbleCell.swift; sourceTree = "<group>"; };
B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = "<group>"; };
B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = "<group>"; };
@ -1648,8 +1751,9 @@
324A2046225FC571004FE8B0 /* Incoming */,
32891D72226728EE00C82226 /* Loading */,
3232AB96225730E100AD6A5C /* Start */,
32891D6D2264DF7B00C82226 /* Verified */,
3232ABAC2257BE6400AD6A5C /* Verify */,
32891D6D2264DF7B00C82226 /* Verified */,
B12D7A0323E43DCC00FACEDC /* KeyVerificationKind.swift */,
3232AB95225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift */,
3232AB9F225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift */,
3232ABA0225730E100AD6A5C /* DeviceVerificationCoordinator.swift */,
@ -1771,6 +1875,17 @@
path = Loading;
sourceTree = "<group>";
};
3291DC8823E0BE380009732F /* Security */ = {
isa = PBXGroup;
children = (
32D5D15C23E1EE2700E3E37C /* ManageSession */,
3291DC8B23E0BFF10009732F /* SecurityViewController.h */,
3291DC8C23E0BFF10009732F /* SecurityViewController.m */,
3291DC8923E0BE820009732F /* Security.storyboard */,
);
path = Security;
sourceTree = "<group>";
};
32935CB21F628B98006888C8 /* js */ = {
isa = PBXGroup;
children = (
@ -1828,6 +1943,16 @@
path = KeyBackup;
sourceTree = "<group>";
};
32D5D15C23E1EE2700E3E37C /* ManageSession */ = {
isa = PBXGroup;
children = (
32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */,
32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */,
32D5D15F23E1EE2700E3E37C /* ManageSessionViewController.h */,
);
path = ManageSession;
sourceTree = "<group>";
};
32DB556722FDADE50016329E /* ServiceTerms */ = {
isa = PBXGroup;
children = (
@ -1911,6 +2036,16 @@
path = Success;
sourceTree = "<group>";
};
B108932623ABE82C00802670 /* BaseContentViews */ = {
isa = PBXGroup;
children = (
B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */,
B14084C523BF76890010F692 /* BubbleCellContentView.swift */,
B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */,
);
path = BaseContentViews;
sourceTree = "<group>";
};
B1098BD921ECE09E000DDA48 /* Generated */ = {
isa = PBXGroup;
children = (
@ -2051,6 +2186,21 @@
path = RoomMessageLinkParser;
sourceTree = "<group>";
};
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 = "<group>";
};
B14F142522144F6400FA0595 /* RecoveryKey */ = {
isa = PBXGroup;
children = (
@ -2395,6 +2545,33 @@
path = Riot/Modules/Common/CollectionView;
sourceTree = SOURCE_ROOT;
};
B1A12C64239AB74500AA2B86 /* CrossSigning */ = {
isa = PBXGroup;
children = (
);
path = CrossSigning;
sourceTree = "<group>";
};
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 = "<group>";
};
B1A6C10523881ECB002882FD /* SlidingModal */ = {
isa = PBXGroup;
children = (
@ -2413,6 +2590,8 @@
B1B5567620EE6C4C00210D55 /* Modules */ = {
isa = PBXGroup;
children = (
B1BEE71023DF28CA0003A4CB /* UserVerification */,
B1A12C64239AB74500AA2B86 /* CrossSigning */,
B1A6C10523881ECB002882FD /* SlidingModal */,
32DB556722FDADE50016329E /* ServiceTerms */,
3232AB94225730E100AD6A5C /* DeviceVerification */,
@ -2457,6 +2636,7 @@
B1B5567B20EE6C4C00210D55 /* Language */,
B1B5567820EE6C4C00210D55 /* PhoneCountry */,
B1B5568020EE6C4C00210D55 /* DeactivateAccount */,
3291DC8823E0BE380009732F /* Security */,
B1CE9EFB22148681000FAE6A /* SignOut */,
);
path = Settings;
@ -2610,6 +2790,7 @@
B1B556A620EE6C4C00210D55 /* Detail */ = {
isa = PBXGroup;
children = (
B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */,
B1B556A720EE6C4C00210D55 /* RoomMemberDetailsViewController.h */,
B1B556A820EE6C4C00210D55 /* RoomMemberDetailsViewController.m */,
B1B556A920EE6C4C00210D55 /* RoomMemberDetailsViewController.xib */,
@ -3249,6 +3430,8 @@
B1B5583F20EF768E00210D55 /* BubbleCells */ = {
isa = PBXGroup;
children = (
B108932623ABE82C00802670 /* BaseContentViews */,
B1A12C65239ABBDB00AA2B86 /* KeyVerification */,
B1B5584220EF768E00210D55 /* Encryption */,
B1B5589220EF768E00210D55 /* RoomEmptyBubbleCell.h */,
B1B558B620EF768E00210D55 /* RoomEmptyBubbleCell.m */,
@ -3564,6 +3747,51 @@
path = ReactionHistory;
sourceTree = "<group>";
};
B1BEE71023DF28CA0003A4CB /* UserVerification */ = {
isa = PBXGroup;
children = (
B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */,
B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */,
B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */,
B12D79F223E2426800FACEDC /* Start */,
B1BEE71723DF2B8A0003A4CB /* SessionsStatus */,
B1BEE73D23E08AC30003A4CB /* SessionStatus */,
);
path = UserVerification;
sourceTree = "<group>";
};
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 = "<group>";
};
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 = "<group>";
};
B1C3361A22F328AE0021BA8D /* Camera */ = {
isa = PBXGroup;
children = (
@ -4061,6 +4289,7 @@
B1A6C113238BD245002882FD /* SlidingModalContainerView.xib in Resources */,
B1B5594420EF7BD000210D55 /* TableViewCellWithCollectionView.xib in Resources */,
B1B558D520EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */,
B12D79FC23E2462200FACEDC /* UserVerificationStartViewController.storyboard in Resources */,
B1B5590020EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib in Resources */,
B1B5597020EFA85D00210D55 /* EncryptionInfoView.xib in Resources */,
B1B558BC20EF768F00210D55 /* RoomMembershipBubbleCell.xib in Resources */,
@ -4083,6 +4312,7 @@
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 */,
@ -4122,10 +4352,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 */,
@ -4150,10 +4382,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 */,
@ -4176,6 +4410,7 @@
B1B558FB20EF768F00210D55 /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib 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 */,
@ -4198,6 +4433,7 @@
B1B557A220EF58AD00210D55 /* ContactTableViewCell.xib in Resources */,
B1B558EB20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib in Resources */,
B10B3B5C2201DD740072C76B /* KeyBackupBannerCell.xib in Resources */,
B1C543A6239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib in Resources */,
32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */,
B1B5581820EF625800210D55 /* PreviewRoomTitleView.xib in Resources */,
B1B5583020EF66BA00210D55 /* RoomIdOrAliasTableViewCell.xib in Resources */,
@ -4441,6 +4677,7 @@
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 */,
@ -4448,6 +4685,7 @@
B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */,
3232ABAA225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift in Sources */,
B1B5574420EE6C4D00210D55 /* CallViewController.m in Sources */,
B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */,
B1B5572220EE6C4D00210D55 /* RoomSettingsViewController.m in Sources */,
B1B5577320EE702800210D55 /* JitsiViewController.m in Sources */,
B169331620F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m in Sources */,
@ -4456,6 +4694,7 @@
32242F1221E8FBA900725742 /* ThemeService.m in Sources */,
B1B558E820EF768F00210D55 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.m in Sources */,
B1B558F320EF768F00210D55 /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m in Sources */,
B12D79FE23E2462200FACEDC /* UserVerificationStartViewController.swift in Sources */,
B1B557BD20EF5B4500210D55 /* KeyboardGrowingTextView.m in Sources */,
B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */,
B1B558F420EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
@ -4469,6 +4708,7 @@
B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */,
B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */,
B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */,
B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */,
B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */,
B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */,
@ -4481,6 +4721,7 @@
B169330B20F3CA3A00746532 /* Contact.m in Sources */,
B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */,
B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */,
B108931F23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift in Sources */,
B1B5599220EFC5E400210D55 /* Analytics.m in Sources */,
B14F143422144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift in Sources */,
B1098BF621ECFE65000DDA48 /* KeyBackupSetupPassphraseCoordinator.swift in Sources */,
@ -4496,6 +4737,7 @@
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,6 +4745,7 @@
B1B558D420EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
B169331420F3CAFC00746532 /* PublicRoomTableViewCell.m in Sources */,
32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */,
3291DC8D23E0BFF10009732F /* SecurityViewController.m in Sources */,
B1C45A88232A8C2600165425 /* SettingsIdentityServerViewState.swift in Sources */,
B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */,
32DB557822FDADE50016329E /* ServiceTermsModalScreenViewState.swift in Sources */,
@ -4520,7 +4763,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 */,
@ -4535,6 +4780,7 @@
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 */,
@ -4570,11 +4816,13 @@
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 */,
B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */,
B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */,
B108932323AB908A00802670 /* KeyVerificationRequestStatusViewData.swift in Sources */,
3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */,
B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */,
B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */,
@ -4588,7 +4836,9 @@
B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */,
B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.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 */,
@ -4599,8 +4849,10 @@
3232AB4F2256558300AD6A5C /* TemplateScreenViewController.swift in Sources */,
B1B558FC20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */,
B1B5572920EE6C4D00210D55 /* RoomFilesViewController.m in Sources */,
B1BEE74B23E093260003A4CB /* UserVerificationSessionStatusViewAction.swift in Sources */,
3232ABBA2257BE6500AD6A5C /* DeviceVerificationVerifyViewModel.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 +4860,12 @@
B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */,
322C110822BBC6F80043FEAC /* WidgetManagerConfig.swift in Sources */,
F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m 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 */,
B1BEE73B23DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift in Sources */,
B1B5583E20EF6E7F00210D55 /* GroupRoomTableViewCell.m in Sources */,
B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */,
B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */,
@ -4619,6 +4873,8 @@
32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */,
B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */,
B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m 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 */,
@ -4629,11 +4885,13 @@
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 */,
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 +4908,22 @@
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 */,
B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */,
B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.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 */,
B14084CA23BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.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 */,
@ -4675,8 +4938,10 @@
B1B5597520EFB02A00210D55 /* InviteRecentTableViewCell.m in Sources */,
B1B5571E20EE6C4D00210D55 /* ContactDetailsViewController.m 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 +4954,7 @@
329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */,
B1098BFB21ECFE65000DDA48 /* KeyBackupSetupCoordinatorType.swift in Sources */,
B1098BF721ECFE65000DDA48 /* PasswordStrength.swift in Sources */,
B1BEE73423DF44A60003A4CB /* UserVerificationSessionsStatusViewModelType.swift in Sources */,
324A2052225FC571004FE8B0 /* DeviceVerificationIncomingViewAction.swift in Sources */,
B105778D2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift in Sources */,
B1B557D820EF5EA900210D55 /* RoomActivitiesView.m in Sources */,
@ -4705,10 +4971,12 @@
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 */,
B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */,
3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */,
B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */,
B108932123AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift in Sources */,
B1B9DEED22EB34EF0065E677 /* ReactionHistoryCoordinator.swift in Sources */,
B1DCC62A22E60D1000625807 /* EmojiMartService.swift in Sources */,
B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */,
@ -4717,6 +4985,7 @@
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 */,
B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */,
@ -4734,6 +5003,7 @@
B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */,
3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */,
B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */,
B1C543B223A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift in Sources */,
323AB947232BD74600C1451F /* AuthFallBackViewController.m in Sources */,
B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */,
B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */,
@ -4745,12 +5015,14 @@
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 */,
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 */,
B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */,
B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */,
@ -4789,6 +5061,7 @@
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 */,
@ -4798,16 +5071,21 @@
B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.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 */,
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 */,
B1DB4F06223015080065DBFA /* Character.swift in Sources */,
B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */,
B1C543A4239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift in Sources */,
32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */,
B12D7A0423E43DCC00FACEDC /* KeyVerificationKind.swift in Sources */,
B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */,
B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */,
B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */,

View file

@ -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

View file

@ -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;
@ -264,6 +266,7 @@ 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))
@ -695,6 +698,9 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
// Observe wrong backup version
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBackupStateDidChangeNotification:) name:kMXKeyBackupDidStateChangeNotification object:nil];
// Observe key verification request
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyVerificationRequestDidChangeNotification:) name:MXDeviceVerificationManagerNewRequestNotification object:nil];
// Resume all existing matrix sessions
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
for (MXKAccount *account in mxAccounts)
@ -713,6 +719,85 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
[self handleLaunchAnimation];
}
- (void)keyVerificationRequestDidChangeNotification:(NSNotification *)notification
{
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground)
{
return;
}
NSDictionary *userInfo = notification.userInfo;
MXKeyVerificationRequest *keyVerificationRequest = userInfo[MXDeviceVerificationManagerNotificationRequestKey];
if ([keyVerificationRequest isKindOfClass:MXKeyVerificationByDMRequest.class])
{
MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)keyVerificationRequest;
if (!keyVerificationByDMRequest.isFromMyUser && keyVerificationByDMRequest.state == MXKeyVerificationRequestStatePending)
{
MXKAccount *currentAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject;
MXRoom *room = [currentAccount.mxSession roomWithRoomId:keyVerificationByDMRequest.roomId];
if (!room)
{
NSLog(@"[AppDelegate][KeyVerification] keyVerificationRequestDidChangeNotification: Unknown room");
return;
}
NSString *sender = keyVerificationByDMRequest.sender;
[room state:^(MXRoomState *roomState) {
NSString *senderName = [roomState.members memberName:sender];
if (self.incomingKeyVerificationRequestAlertController)
{
[self.incomingKeyVerificationRequestAlertController dismissViewControllerAnimated:NO completion:nil];
}
NSMutableString *senderInfo = [NSMutableString stringWithString:sender];
if (senderName)
{
[senderInfo appendFormat:@" (%@)", senderName];
}
NSString *alertMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"key_verification_incoming_request_incoming_alert_message", @"Vector", nil), senderInfo];
self.incomingKeyVerificationRequestAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_title", @"Vector", nil)
message:alertMessage
preferredStyle:UIAlertControllerStyleAlert];
[self.incomingKeyVerificationRequestAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_approval_accept", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[self presentIncomingKeyVerificationRequest:keyVerificationByDMRequest inSession:self.mxSessions.firstObject];
}]];
[self.incomingKeyVerificationRequestAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_approval_decline", @"Vector", nil)
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * action)
{
[keyVerificationByDMRequest cancelWithCancelCode:MXTransactionCancelCode.user success:^{
} failure:^(NSError * _Nonnull error) {
NSLog(@"[AppDelegate][KeyVerification] Fail to cancel incoming key verification request with error: %@", error);
}];
}]];
[self.incomingKeyVerificationRequestAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"later", @"Vector", nil)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action)
{
}]];
[self showNotificationAlert:self.incomingKeyVerificationRequestAlertController];
}];
}
}
}
- (void)applicationWillTerminate:(UIApplication *)application
{
NSLog(@"[AppDelegate] applicationWillTerminate");
@ -1600,132 +1685,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 +1710,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 +1757,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 +1777,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.deviceVerificationManager 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 +1876,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 copy]);
onComplete(notificationContent);
}
}];
}
- (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).
@ -2795,6 +2802,9 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
break;
}
}
// Do not warn for unknown devices if cross-signing is enabled
mxSession.crypto.warnOnUnknowDevices = !RiotSettings.shared.enableCrossSigning;
}
else if (mxSession.state == MXSessionStateClosed)
{
@ -3835,37 +3845,45 @@ 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) {
void (^onFailure)(NSError *) = ^(NSError *error){
NSLog(@"[AppDelegate] Create direct chat failed");
//Alert user
[self showErrorAsAlert:error];
// Open created room
[self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession];
if (completion)
{
completion();
}
};
if (completion)
{
completion();
}
[mxSession canEnableE2EByDefaultInNewRoomWithUsers:invite success:^(BOOL canEnableE2E) {
}
failure:^(NSError *error) {
MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new];
roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate;
roomCreationParameters.inviteArray = invite;
roomCreationParameters.isDirect = (invite.count != 0);
roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat;
NSLog(@"[AppDelegate] Create direct chat failed");
//Alert user
[self showErrorAsAlert:error];
if (canEnableE2E)
{
roomCreationParameters.initialStateEvents = @[
[MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm
]];
}
if (completion)
{
completion();
}
[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)
{
@ -4701,12 +4719,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<MXDeviceInfo *> *usersDevicesInfoMap) {
[mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap, NSDictionary<NSString *,MXCrossSigningInfo *> *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()
{
@ -4826,6 +4844,32 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
}
}
- (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest
inSession:(MXSession*)session
{
BOOL presented = NO;
if (!deviceVerificationCoordinatorBridgePresenter)
{
NSLog(@"[AppDelegate] presentIncomingKeyVerificationRequest");
UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController;
deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:session];
deviceVerificationCoordinatorBridgePresenter.delegate = self;
[deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController incomingKeyVerificationRequest:incomingKeyVerificationRequest animated:YES];
presented = YES;
}
else
{
NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingDeviceVerification: Controller already presented.");
}
return presented;
}
- (BOOL)presentIncomingDeviceVerification:(MXIncomingSASTransaction*)transaction inSession:(MXSession*)mxSession
{
NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingDeviceVerification: %@", transaction);
@ -4849,6 +4893,29 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
return presented;
}
- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession
{
NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: %@", roomMember);
BOOL presented = NO;
if (!deviceVerificationCoordinatorBridgePresenter)
{
UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController;
deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession];
deviceVerificationCoordinatorBridgePresenter.delegate = self;
[deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController roomMember:roomMember animated:YES];
presented = YES;
}
else
{
NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: Controller already presented.");
}
return presented;
}
- (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId
{
[deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

View file

@ -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";

View file

@ -240,7 +240,8 @@
"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_other" = "Options";
"room_participants_action_section_security" = "Security";
"room_participants_action_invite" = "Invite";
"room_participants_action_leave" = "Leave this room";
@ -256,6 +257,13 @@
"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_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.";
// Chat
"room_jump_to_first_unread" = "Jump to first unread message";
@ -416,6 +424,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 +468,7 @@
"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_labs_enable_cross_signing" = "Enable cross-signing to verify per-user instead of per-device (in development)";
"settings_version" = "Version %@";
"settings_olm_version" = "Olm Version %@";
@ -543,6 +552,28 @@
"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_description" = "Trust sessions to grant access to end-to-end encrypted messages. If you dont recognise a session, change your login password and reset your Message Password used for Message Backup.";
"security_settings_backup" = "MESSAGE BACKUP";
"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.";
"security_settings_export_keys_manually" = "Export keys manually";
// Manage session
"manage_session_title" = "Manage session";
"manage_session_info" = "SESSION INFO";
"manage_session_name" = "Device name";
"manage_session_trusted" = "Trusted by you";
"manage_session_not_trusted" = "Not trusted";
"manage_session_sign_out" = "Sign out of this device";
// Identity server settings
"identity_server_settings_title" = "Identity Server";
@ -970,6 +1001,8 @@
// MARK: - Device Verification
"device_verification_title" = "Verify device";
"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: %@";
@ -988,16 +1021,32 @@
"device_verification_start_use_legacy_action" = "Use Legacy Verification";
// MARK: Verify
// Device
"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...";
// 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_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" = "Youve successfully verified this user.";
"key_verification_verified_user_description_2" = "Messages with this user in this room are end-to-end encrypted and cant be read by third parties.";
// MARK: Emoji
"device_verification_emoji_dog" = "Dog";
"device_verification_emoji_cat" = "Cat";
@ -1087,3 +1136,65 @@
// 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
// 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: - 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 cant 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 device 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 device:";
"user_verification_session_details_additional_information_untrusted_other_user" = "Until this user trusts this device, 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 didnt 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";

View file

@ -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.
*/

View file

@ -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];

View file

@ -17,6 +17,8 @@
#import <MatrixKit/MatrixKit.h>
#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

View file

@ -324,6 +324,37 @@
}
}
- (UserEncryptionTrustLevel)encryptionTrustLevelForUserId:(NSString*)userId
{
UserEncryptionTrustLevel userEncryptionTrustLevel;
if (self.summary.isEncrypted && 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

View file

@ -16,6 +16,17 @@
#import <MatrixKit/MatrixKit.h>
/**
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

View file

@ -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

View file

@ -17,6 +17,7 @@
#import "MXSession+Riot.h"
#import "MXRoom+Riot.h"
#import "Riot-Swift.h"
@implementation MXSession (Riot)

View file

@ -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)

View file

@ -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()
}
}
}

View file

@ -50,6 +50,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")

View file

@ -117,6 +117,21 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.TemplateScreenViewController>(storyboard: TemplateScreenViewController.self)
}
internal enum UserVerificationSessionStatusViewController: StoryboardType {
internal static let storyboardName = "UserVerificationSessionStatusViewController"
internal static let initialScene = InitialSceneType<Riot.UserVerificationSessionStatusViewController>(storyboard: UserVerificationSessionStatusViewController.self)
}
internal enum UserVerificationSessionsStatusViewController: StoryboardType {
internal static let storyboardName = "UserVerificationSessionsStatusViewController"
internal static let initialScene = InitialSceneType<Riot.UserVerificationSessionsStatusViewController>(storyboard: UserVerificationSessionsStatusViewController.self)
}
internal enum UserVerificationStartViewController: StoryboardType {
internal static let storyboardName = "UserVerificationStartViewController"
internal static let initialScene = InitialSceneType<Riot.UserVerificationStartViewController>(storyboard: UserVerificationStartViewController.self)
}
internal enum WidgetPermissionViewController: StoryboardType {
internal static let storyboardName = "WidgetPermissionViewController"

View file

@ -1470,6 +1470,78 @@ internal enum VectorL10n {
internal static var keyBackupSetupTitle: String {
return VectorL10n.tr("Vector", "key_backup_setup_title")
}
/// %@ wants to verify
internal static func keyVerificationIncomingRequestIncomingAlertMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "key_verification_incoming_request_incoming_alert_message", 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")
}
/// Youve 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 cant be read by third parties.
internal static var keyVerificationVerifiedUserDescription2: String {
return VectorL10n.tr("Vector", "key_verification_verified_user_description_2")
}
/// 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 +1554,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")
}
/// Device 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 device
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")
@ -2178,10 +2274,26 @@ 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")
}
/// 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 +2406,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")
@ -2554,6 +2678,38 @@ 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")
}
/// 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 dont 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")
}
/// 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)
@ -2970,14 +3126,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")
@ -2986,6 +3134,10 @@ internal enum VectorL10n {
internal static var settingsLabsE2eEncryptionPromptMessage: String {
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message")
}
/// Enable cross-signing to verify per-user instead of per-device (in development)
internal static var settingsLabsEnableCrossSigning: String {
return VectorL10n.tr("Vector", "settings_labs_enable_cross_signing")
}
/// React to messages with emoji
internal static var settingsLabsMessageReaction: String {
return VectorL10n.tr("Vector", "settings_labs_message_reaction")
@ -3074,6 +3226,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")
@ -3294,6 +3450,98 @@ internal enum VectorL10n {
internal static var unknownDevicesVerify: String {
return VectorL10n.tr("Vector", "unknown_devices_verify")
}
/// If you didnt 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 device, 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 device 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 device:
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 cant 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")

View file

@ -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:

View file

@ -124,14 +124,6 @@ final class RiotSettings: NSObject {
}
}
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)

View file

@ -1110,20 +1110,10 @@
{
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"];
[account.mxSession createRoomWithParameters:roomCreationParameters success:nil failure:^(NSError *error) {
NSLog(@"[AuthenticationVC] Create chat with riot-bot failed");
}];
}
// Remove auth view controller on successful login

View file

@ -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

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -75,10 +75,10 @@
<nil key="highlightedColor"/>
</label>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OeZ-wN-eil">
<rect key="frame" x="524" y="14" width="0.0" height="20"/>
<rect key="frame" x="524" y="13.5" width="0.0" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mbn-A1-1Yw">
<rect key="frame" x="-4" y="2" width="9" height="17"/>
<rect key="frame" x="-4.5" y="1.5" width="9" height="17"/>
<accessibility key="accessibilityConfiguration" identifier="MissedNotifAndUnreadBadge"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -94,11 +94,11 @@
</constraints>
</view>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="e2e_verified" translatesAutoresizingMaskIntoConstraints="NO" id="NAZ-zd-MHS">
<rect key="frame" x="50" y="42" width="11" height="13"/>
<rect key="frame" x="44" y="43" width="16" height="16"/>
<accessibility key="accessibilityConfiguration" identifier="EncryptedRoomIcon"/>
<constraints>
<constraint firstAttribute="height" constant="13" id="QdE-73-LyZ"/>
<constraint firstAttribute="width" constant="11" id="i9u-Bs-pBy"/>
<constraint firstAttribute="height" constant="16" id="QdE-73-LyZ"/>
<constraint firstAttribute="width" constant="16" id="i9u-Bs-pBy"/>
</constraints>
</imageView>
</subviews>
@ -112,11 +112,11 @@
<constraint firstItem="dQt-mN-T6b" firstAttribute="leading" secondItem="RX5-eD-c3c" secondAttribute="trailing" constant="14" id="XFM-LG-4uJ"/>
<constraint firstItem="360-Go-RcG" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="16" id="XyO-tl-6SX"/>
<constraint firstAttribute="trailing" secondItem="360-Go-RcG" secondAttribute="trailing" constant="10" id="YqC-WC-Wqe"/>
<constraint firstItem="NAZ-zd-MHS" firstAttribute="centerY" secondItem="dQt-mN-T6b" secondAttribute="centerY" id="c6L-cn-sJL"/>
<constraint firstItem="NAZ-zd-MHS" firstAttribute="centerY" secondItem="dQt-mN-T6b" secondAttribute="centerY" constant="2" id="c6L-cn-sJL"/>
<constraint firstItem="OeZ-wN-eil" firstAttribute="centerY" secondItem="360-Go-RcG" secondAttribute="centerY" id="fYc-th-Nay"/>
<constraint firstItem="dQt-mN-T6b" firstAttribute="top" secondItem="Lg1-xQ-AGn" secondAttribute="bottom" constant="4" id="iaT-57-GOs"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="15" id="mga-fG-I0L"/>
<constraint firstItem="NAZ-zd-MHS" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="50" id="ofi-HP-uke"/>
<constraint firstItem="NAZ-zd-MHS" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="44" id="ofi-HP-uke"/>
<constraint firstAttribute="trailing" secondItem="dQt-mN-T6b" secondAttribute="trailing" constant="10" id="t2m-pb-5zd"/>
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="14" id="tY3-6V-A3B"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="13" id="tgy-cX-Wxm"/>

View file

@ -20,6 +20,7 @@
#import "AppDelegate.h"
#import "Riot-Swift.h"
#import "MXSession+Riot.h"
#import "RoomMemberTitleView.h"
@ -1041,36 +1042,51 @@
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) {
[self.mainSession canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) {
MXStrongifyAndReturnIfNil(self);
roomCreationRequest = nil;
MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new];
roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate;
roomCreationParameters.inviteArray = inviteArray;
roomCreationParameters.invite3PIDArray = invite3PIDArray;
roomCreationParameters.isDirect = YES;
roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat;
[self removePendingActionMask];
if (canEnableE2E && roomCreationParameters.invite3PIDArray == nil)
{
roomCreationParameters.initialStateEvents = @[
[MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm
]];
}
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession];
}
failure:^(NSError *error) {
self->roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) {
NSLog(@"[ContactDetailsViewController] Create room failed");
self->roomCreationRequest = nil;
roomCreationRequest = nil;
[self removePendingActionMask];
[self removePendingActionMask];
[[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession];
// Notify user
[[AppDelegate theDelegate] showErrorAsAlert:error];
} 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) {
MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:matrixId];
roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) {
roomCreationRequest = nil;
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];
});
// 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) {
} failure:^(NSError *error) {
NSLog(@"[ContactDetailsViewController] Create room failed");
NSLog(@"[ContactDetailsViewController] Create room failed");
roomCreationRequest = nil;
roomCreationRequest = nil;
[self removePendingActionMask];
[self removePendingActionMask];
// Notify user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
// Notify user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}
break;
}

View file

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -57,14 +55,15 @@
</subviews>
<constraints>
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="leading" secondItem="FfX-ul-Kr4" secondAttribute="trailing" constant="14" id="A6H-TC-2Pg"/>
<constraint firstAttribute="bottom" secondItem="FfX-ul-Kr4" secondAttribute="bottom" constant="15.5" id="D2o-qq-OsZ"/>
<constraint firstItem="O8i-B6-S6A" firstAttribute="centerY" secondItem="FfX-ul-Kr4" secondAttribute="centerY" id="L8U-Xi-rD7"/>
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="centerY" secondItem="aXz-IR-jj5" secondAttribute="centerY" id="O6E-Di-2d4"/>
<constraint firstItem="apY-Nk-wQh" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="topMargin" constant="34" id="Rlm-bQ-Qpr"/>
<constraint firstItem="O8i-B6-S6A" firstAttribute="width" secondItem="FfX-ul-Kr4" secondAttribute="width" id="Sej-VT-sBx"/>
<constraint firstAttribute="trailingMargin" relation="greaterThanOrEqual" secondItem="Lg1-xQ-AGn" secondAttribute="trailing" constant="15" id="U1F-vo-7f6"/>
<constraint firstItem="apY-Nk-wQh" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leadingMargin" constant="42" id="dut-Df-DIU"/>
<constraint firstItem="FfX-ul-Kr4" firstAttribute="centerY" secondItem="aXz-IR-jj5" secondAttribute="centerY" id="gUw-GV-DPX"/>
<constraint firstItem="FfX-ul-Kr4" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leadingMargin" constant="6" id="qey-6T-URF"/>
<constraint firstItem="FfX-ul-Kr4" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="16" id="wyT-JI-kQS"/>
<constraint firstItem="O8i-B6-S6A" firstAttribute="centerX" secondItem="FfX-ul-Kr4" secondAttribute="centerX" id="xfK-sI-YJQ"/>
</constraints>
</tableViewCellContentView>

View file

@ -33,6 +33,7 @@
@property (nonatomic) IBOutlet UILabel *contactDisplayNameLabel;
@property (nonatomic) IBOutlet UILabel *contactInformationLabel;
@property (nonatomic) IBOutlet UIView *customAccessoryView;
@property (weak, nonatomic) IBOutlet UIImageView *avatarBadgeImageView;
@property (nonatomic) BOOL showCustomAccessoryView;

View file

@ -24,6 +24,7 @@
#import "AvatarGenerator.h"
#import "Tools.h"
#import "MXRoom+Riot.h"
#import "NBPhoneNumberUtil.h"
@ -171,6 +172,7 @@
}];
[self refreshContactPresence];
[self refreshContactBadgeImage];
}
else
{
@ -234,6 +236,47 @@
self.thumbnailView.image = image;
}
- (void)refreshContactBadgeImage
{
NSString *matrixId = [self firstMatrixId];
UserEncryptionTrustLevel userEncryptionTrustLevel = UserEncryptionTrustLevelUnknown;
if (matrixId)
{
userEncryptionTrustLevel = [self.mxRoom encryptionTrustLevelForUserId:matrixId];
}
self.avatarBadgeImageView.image = [self badgeImageForUserEncryptionTrustLevel:userEncryptionTrustLevel];
}
- (UIImage*)badgeImageForUserEncryptionTrustLevel:(UserEncryptionTrustLevel)userEncryptionTrustLevel
{
NSString *encryptionIconName;
UIImage *encryptionIcon;
switch (userEncryptionTrustLevel) {
case UserEncryptionTrustLevelWarning:
encryptionIconName = @"encryption_warning";
break;
case UserEncryptionTrustLevelNormal:
encryptionIconName = @"encryption_normal";
break;
case UserEncryptionTrustLevelTrusted:
encryptionIconName = @"encryption_trusted";
break;
default:
break;
}
if (encryptionIconName)
{
encryptionIcon = [UIImage imageNamed:encryptionIconName];
}
return encryptionIcon;
}
- (void)refreshContactDisplayName
{
self.contactDisplayNameLabel.text = contact.displayName;

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -27,6 +27,13 @@
<constraint firstAttribute="height" constant="42" id="WPC-tL-hnM"/>
</constraints>
</view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Wg7-no-cax">
<rect key="frame" x="41" y="43" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="KYi-Aj-oCp"/>
<constraint firstAttribute="width" secondItem="Wg7-no-cax" secondAttribute="height" multiplier="1:1" id="Lh5-LD-3eg"/>
</constraints>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="e2Q-sr-Kz4">
<rect key="frame" x="42" y="10" width="20" height="21"/>
<accessibility key="accessibilityConfiguration" identifier="ThumbnailBadgeView"/>
@ -43,7 +50,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="info label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQt-mN-T6b">
<rect key="frame" x="69" y="39" width="531" height="20"/>
<rect key="frame" x="69" y="39" width="531" height="19.5"/>
<accessibility key="accessibilityConfiguration" identifier="ContactInformationLabel"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -60,6 +67,7 @@
</view>
</subviews>
<constraints>
<constraint firstItem="Wg7-no-cax" firstAttribute="width" secondItem="Wg7-no-cax" secondAttribute="height" multiplier="1:1" id="07s-nq-mon"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="RX5-eD-c3c" secondAttribute="bottom" constant="15" id="0MH-4P-AYF"/>
<constraint firstAttribute="bottom" secondItem="dQt-mN-T6b" secondAttribute="bottom" constant="15" id="2cU-tU-nDT"/>
<constraint firstItem="e2Q-sr-Kz4" firstAttribute="trailing" secondItem="RX5-eD-c3c" secondAttribute="trailing" constant="7" id="Fib-p7-t8G"/>
@ -68,10 +76,13 @@
<constraint firstItem="dQt-mN-T6b" firstAttribute="top" secondItem="Lg1-xQ-AGn" secondAttribute="bottom" constant="4" id="iaT-57-GOs"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="15" id="mga-fG-I0L"/>
<constraint firstItem="Ogo-Qt-u2C" firstAttribute="leading" secondItem="Lg1-xQ-AGn" secondAttribute="trailing" constant="13" id="oNi-JM-zCJ"/>
<constraint firstItem="Wg7-no-cax" firstAttribute="bottom" secondItem="RX5-eD-c3c" secondAttribute="bottom" constant="2" id="pck-mZ-FjJ"/>
<constraint firstItem="Wg7-no-cax" firstAttribute="width" secondItem="Wg7-no-cax" secondAttribute="height" multiplier="1:1" id="sZo-F2-fpL"/>
<constraint firstAttribute="trailing" secondItem="dQt-mN-T6b" secondAttribute="trailing" id="t2m-pb-5zd"/>
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="14" id="tY3-6V-A3B"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="13" id="tgy-cX-Wxm"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="top" secondItem="e2Q-sr-Kz4" secondAttribute="top" constant="5" id="xrs-nE-h6t"/>
<constraint firstItem="Wg7-no-cax" firstAttribute="trailing" secondItem="RX5-eD-c3c" secondAttribute="trailing" constant="2" id="zT4-CH-fH8"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -81,6 +92,7 @@
<constraint firstAttribute="trailing" secondItem="Ogo-Qt-u2C" secondAttribute="trailing" constant="13" id="cWl-2f-cvI"/>
</constraints>
<connections>
<outlet property="avatarBadgeImageView" destination="Wg7-no-cax" id="mTr-4H-B6t"/>
<outlet property="contactDisplayNameLabel" destination="Lg1-xQ-AGn" id="xKB-Pw-12c"/>
<outlet property="contactInformationLabel" destination="dQt-mN-T6b" id="IKZ-wv-xVD"/>
<outlet property="customAccessViewWidthConstraint" destination="pDU-SS-0mb" id="A22-tz-iIz"/>

View file

@ -31,6 +31,10 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
private let otherDeviceId: String
private var incomingTransaction: MXIncomingSASTransaction?
private var incomingKeyVerificationRequest: MXKeyVerificationRequest?
private var verificationKind: KeyVerificationKind = .device
private var roomMember: MXRoomMember?
// MARK: Public
@ -54,6 +58,13 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
self.otherDeviceId = otherDeviceId
}
init(navigationRouter: NavigationRouterType, session: MXSession, userId: String, otherDeviceId: String) {
self.navigationRouter = navigationRouter
self.session = session
self.otherUserId = userId
self.otherDeviceId = otherDeviceId
}
/// Contrustor to manage an incoming SAS device verification transaction
///
/// - Parameters:
@ -66,15 +77,55 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
self.incomingTransaction = incomingTransaction
}
/// Contrustor to manage an incoming SAS device verification transaction
///
/// - Parameters:
/// - session: the MXSession
/// - incomingKeyVerificationRequest: An existing incoming key verification request to accept
convenience init(session: MXSession, incomingKeyVerificationRequest: MXKeyVerificationRequest) {
self.init(session: session, otherUserId: incomingKeyVerificationRequest.sender, otherDeviceId: incomingKeyVerificationRequest.fromDevice)
self.incomingKeyVerificationRequest = incomingKeyVerificationRequest
}
/// Constructor to start a user verification.
///
/// - Parameters:
/// - session: the MXSession
/// - roomMember: an other room member
init(session: MXSession, roomMember: MXRoomMember) {
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
self.session = session
self.otherUserId = roomMember.userId
self.otherDeviceId = ""
self.roomMember = roomMember
self.verificationKind = .user
}
// MARK: - Public methods
func start() {
let rootCoordinator = self.createDataLoadingScreenCoordinator()
let rootCoordinator: Coordinator & Presentable
if let incomingKeyVerificationRequest = self.incomingKeyVerificationRequest {
rootCoordinator = self.createDataLoadingScreenCoordinator(with: incomingKeyVerificationRequest)
} else if let roomMember = self.roomMember {
rootCoordinator = self.createUserVerificationStartCoordinator(with: roomMember)
} else {
rootCoordinator = self.createDataLoadingScreenCoordinator()
}
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in
self?.remove(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)
}
}
}
@ -92,6 +143,22 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
return coordinator
}
private func createDataLoadingScreenCoordinator(with keyVerificationRequest: MXKeyVerificationRequest) -> DeviceVerificationDataLoadingCoordinator {
let coordinator = DeviceVerificationDataLoadingCoordinator(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
@ -115,7 +182,7 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
}
private func showVerify(transaction: MXSASTransaction, animated: Bool) {
let coordinator = DeviceVerificationVerifyCoordinator(session: self.session, transaction: transaction)
let coordinator = DeviceVerificationVerifyCoordinator(session: self.session, transaction: transaction, verificationKind: self.verificationKind)
coordinator.delegate = self
coordinator.start()
@ -126,7 +193,7 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
}
private func showVerified(animated: Bool) {
let viewController = DeviceVerificationVerifiedViewController.instantiate()
let viewController = DeviceVerificationVerifiedViewController.instantiate(with: self.verificationKind)
viewController.delegate = self
self.navigationRouter.setRootModule(viewController)
}
@ -142,6 +209,16 @@ extension DeviceVerificationCoordinator: DeviceVerificationDataLoadingCoordinato
}
}
func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXDeviceVerificationTransaction) {
if let sasTransaction = transaction as? MXSASTransaction {
self.showVerify(transaction: sasTransaction, animated: true)
} else {
NSLog("[DeviceVerificationCoordinator] Transaction \(transaction) is not supported")
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
@ -190,3 +267,17 @@ extension DeviceVerificationCoordinator: DeviceVerificationVerifiedViewControlle
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}
extension DeviceVerificationCoordinator: UserVerificationStartCoordinatorDelegate {
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) {
self.showVerify(transaction: transaction, animated: true)
}
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType) {
self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId)
}
}

View file

@ -64,6 +64,18 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject {
self.coordinator = deviceVerificationCoordinator
}
func present(from viewController: UIViewController, roomMember: MXRoomMember, animated: Bool) {
NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present from \(viewController)")
let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, roomMember: roomMember)
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)")
@ -76,6 +88,18 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject {
self.coordinator = deviceVerificationCoordinator
}
func present(from viewController: UIViewController, incomingKeyVerificationRequest: MXKeyVerificationRequest, animated: Bool) {
NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present incoming key verification request from \(viewController)")
let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, incomingKeyVerificationRequest: incomingKeyVerificationRequest)
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

View file

@ -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
}

View file

@ -25,7 +25,6 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad
// MARK: Private
private let session: MXSession
private var deviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType
private let deviceVerificationDataLoadingViewController: DeviceVerificationDataLoadingViewController
@ -39,9 +38,14 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad
// MARK: - Setup
init(session: MXSession, otherUserId: String, otherDeviceId: String) {
self.session = session
let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(session: session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
let deviceVerificationDataLoadingViewController = DeviceVerificationDataLoadingViewController.instantiate(with: deviceVerificationDataLoadingViewModel)
self.deviceVerificationDataLoadingViewModel = deviceVerificationDataLoadingViewModel
self.deviceVerificationDataLoadingViewController = deviceVerificationDataLoadingViewController
}
let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId)
init(incomingKeyVerificationRequest: MXKeyVerificationRequest) {
let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(keyVerificationRequest: incomingKeyVerificationRequest)
let deviceVerificationDataLoadingViewController = DeviceVerificationDataLoadingViewController.instantiate(with: deviceVerificationDataLoadingViewModel)
self.deviceVerificationDataLoadingViewModel = deviceVerificationDataLoadingViewModel
self.deviceVerificationDataLoadingViewController = deviceVerificationDataLoadingViewController
@ -60,6 +64,11 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad
// MARK: - DeviceVerificationDataLoadingViewModelCoordinatorDelegate
extension DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingViewModelCoordinatorDelegate {
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXDeviceVerificationTransaction) {
self.delegate?.deviceVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequestWithTransaction: transaction)
}
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) {
self.delegate?.deviceVerificationDataLoadingCoordinator(self, didLoadUser: user, device: device)
}

View file

@ -20,6 +20,7 @@ import Foundation
protocol DeviceVerificationDataLoadingCoordinatorDelegate: class {
func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo)
func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXDeviceVerificationTransaction)
func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType)
}

View file

@ -66,6 +66,13 @@ final class DeviceVerificationDataLoadingViewController: UIViewController {
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) {
@ -112,9 +119,35 @@ final class DeviceVerificationDataLoadingViewController: UIViewController {
}
private func render(error: Error) {
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: {
self.viewModel.process(viewAction: .cancel)
})
var shouldDisplayError = true
var message: String?
switch error {
case DeviceVerificationDataLoadingViewModelError.transactionCancelled:
message = VectorL10n.deviceVerificationCancelled
case DeviceVerificationDataLoadingViewModelError.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) {

View file

@ -20,6 +20,8 @@ import Foundation
enum DeviceVerificationDataLoadingViewModelError: Error {
case unknown
case transactionCancelled
case transactionCancelledByMe(reason: MXTransactionCancelCode)
}
final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType {
@ -28,9 +30,13 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin
// MARK: Private
private let session: MXSession
private let otherUserId: String
private let otherDeviceId: String
private let session: MXSession?
private let otherUserId: String?
private let otherDeviceId: String?
private let keyVerificationRequest: MXKeyVerificationRequest?
private var currentOperation: MXHTTPOperation?
// MARK: Public
@ -43,9 +49,18 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin
self.session = session
self.otherUserId = otherUserId
self.otherDeviceId = otherDeviceId
self.keyVerificationRequest = nil
}
init(keyVerificationRequest: MXKeyVerificationRequest) {
self.session = nil
self.otherUserId = nil
self.otherDeviceId = nil
self.keyVerificationRequest = keyVerificationRequest
}
deinit {
self.currentOperation?.cancel()
}
// MARK: - Public
@ -62,36 +77,70 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin
// MARK: - Private
private func loadData() {
guard let crypto = self.session.crypto else {
if let keyVerificationRequest = self.keyVerificationRequest {
self.acceptKeyVerificationRequest(keyVerificationRequest)
} else {
self.downloadOtherDeviceKeys()
}
}
private func acceptKeyVerificationRequest(_ keyVerificationRequest: MXKeyVerificationRequest) {
self.update(viewState: .loading)
keyVerificationRequest.accept(withMethod: MXKeyVerificationMethodSAS, success: { [weak self] (deviceVerificationTransaction) in
guard let self = self else {
return
}
if let outgoingSASTransaction = deviceVerificationTransaction as? MXOutgoingSASTransaction {
self.registerTransactionDidStateChangeNotification(transaction: outgoingSASTransaction)
} else {
self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.unknown))
}
}, failure: { [weak self] (error) in
guard let self = self else {
return
}
self.update(viewState: .error(error))
})
}
private func downloadOtherDeviceKeys() {
guard let session = self.session,
let crypto = session.crypto,
let otherUserId = self.otherUserId,
let otherDeviceId = self.otherDeviceId else {
self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
NSLog("[DeviceVerificationDataLoadingViewModel] Error session.crypto is nil")
return
}
if let otherUser = self.session.user(withUserId: otherUserId) {
if let otherUser = session.user(withUserId: otherUserId) {
self.update(viewState: .loading)
crypto.downloadKeys([self.otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap) in
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: sself.otherDeviceId, forUser: sself.otherUserId) {
if let otherDevice = usersDevicesMap?.object(forDevice: otherDeviceId, forUser: otherUserId) {
sself.update(viewState: .loaded)
sself.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(sself, didLoadUser: otherUser, device: otherDevice)
} else {
sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice))
}
}, failure: { [weak self] (error) in
guard let sself = self else {
return
}
}, failure: { [weak self] (error) in
guard let sself = self else {
return
}
let finalError = error ?? DeviceVerificationDataLoadingViewModelError.unknown
let finalError = error ?? DeviceVerificationDataLoadingViewModelError.unknown
sself.update(viewState: .error(finalError))
sself.update(viewState: .error(finalError))
})
} else {
@ -102,4 +151,38 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin
private func update(viewState: DeviceVerificationDataLoadingViewState) {
self.viewDelegate?.deviceVerificationDataLoadingViewModel(self, didUpdateViewState: viewState)
}
// MARK: MXDeviceVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification(transaction: MXOutgoingSASTransaction) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXOutgoingSASTransaction else {
return
}
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .loaded)
self.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(self, didAcceptKeyVerificationWithTransaction: transaction)
case MXSASTransactionStateCancelled:
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.transactionCancelled))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.transactionCancelledByMe(reason: reason)))
default:
break
}
}
}

View file

@ -24,6 +24,7 @@ protocol DeviceVerificationDataLoadingViewModelViewDelegate: class {
protocol DeviceVerificationDataLoadingViewModelCoordinatorDelegate: class {
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo)
func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXDeviceVerificationTransaction)
func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType)
}

View file

@ -39,6 +39,7 @@ final class DeviceVerificationVerifiedViewController: UIViewController {
// MARK: Private
private var theme: Theme!
private var verificationKind: KeyVerificationKind = .user
// MARK: Public
@ -46,9 +47,10 @@ final class DeviceVerificationVerifiedViewController: UIViewController {
// MARK: - Setup
class func instantiate() -> DeviceVerificationVerifiedViewController {
class func instantiate(with verificationKind: KeyVerificationKind) -> DeviceVerificationVerifiedViewController {
let viewController = StoryboardScene.DeviceVerificationVerifiedViewController.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)

View file

@ -38,10 +38,10 @@ final class DeviceVerificationVerifyCoordinator: DeviceVerificationVerifyCoordin
// MARK: - Setup
init(session: MXSession, transaction: MXSASTransaction) {
init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) {
self.session = session
let deviceVerificationVerifyViewModel = DeviceVerificationVerifyViewModel(session: self.session, transaction: transaction)
let deviceVerificationVerifyViewModel = DeviceVerificationVerifyViewModel(session: self.session, transaction: transaction, verificationKind: verificationKind)
let deviceVerificationVerifyViewController = DeviceVerificationVerifyViewController.instantiate(with: deviceVerificationVerifyViewModel)
self.deviceVerificationVerifyViewModel = deviceVerificationVerifyViewModel
self.deviceVerificationVerifyViewController = deviceVerificationVerifyViewController

View file

@ -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
}
self.informationLabel.text = VectorL10n.deviceVerificationSecurityAdvice
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.title = title
self.titleLabel.text = instructionText
self.informationLabel.text = adviceText
self.waitingPartnerLabel.text = VectorL10n.deviceVerificationVerifyWaitPartner
self.waitingPartnerLabel.isHidden = true
@ -224,9 +240,7 @@ 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()

View file

@ -31,16 +31,19 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel
weak var viewDelegate: DeviceVerificationVerifyViewModelViewDelegate?
weak var coordinatorDelegate: DeviceVerificationVerifyViewModelCoordinatorDelegate?
var emojis: [MXEmojiRepresentation]?
var decimal: String?
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 {

View file

@ -35,6 +35,7 @@ protocol DeviceVerificationVerifyViewModelType {
func process(viewAction: DeviceVerificationVerifyViewAction)
var emojis: [MXEmojiRepresentation]? { get set }
var decimal: String? { get set }
var emojis: [MXEmojiRepresentation]? { get }
var decimal: String? { get }
var verificationKind: KeyVerificationKind { get }
}

View file

@ -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!

View file

@ -54,7 +54,7 @@
- (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)
{

View file

@ -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 = [self shieldImageForTrustLevel:roomCellData.roomSummary.roomEncryptionTrustLevel];
}
else
{
self.encryptedRoomIcon.hidden = YES;
}
[roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar];
}
@ -207,5 +215,33 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
return nil;
}
- (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

View file

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -39,7 +38,7 @@
<rect key="frame" x="80" y="5" width="0.0" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZUZ-tv-dVV">
<rect key="frame" x="-4" y="2" width="9" height="17"/>
<rect key="frame" x="-4.5" y="1.5" width="9" height="17"/>
<accessibility key="accessibilityConfiguration" identifier="MissedNotifAndUnreadBadge"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -55,10 +54,10 @@
</constraints>
</view>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="e2e_verified" translatesAutoresizingMaskIntoConstraints="NO" id="5Yd-df-HbB">
<rect key="frame" x="53" y="55" width="15" height="18"/>
<rect key="frame" x="48" y="50" width="24" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="UEZ-5S-KMf"/>
<constraint firstAttribute="width" constant="15" id="bbS-D2-SZi"/>
<constraint firstAttribute="height" constant="24" id="UEZ-5S-KMf"/>
<constraint firstAttribute="width" constant="24" id="bbS-D2-SZi"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="RoomTitle" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oxX-IL-dG4">
@ -93,7 +92,7 @@
</subviews>
</view>
<constraints>
<constraint firstAttribute="trailing" secondItem="5Yd-df-HbB" secondAttribute="trailing" constant="12" id="0gr-xn-dfD"/>
<constraint firstAttribute="trailing" secondItem="5Yd-df-HbB" secondAttribute="trailing" constant="8" id="0gr-xn-dfD"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="oxX-IL-dG4" secondAttribute="bottom" constant="5" id="1JB-d1-zb9"/>
<constraint firstItem="oxX-IL-dG4" firstAttribute="top" secondItem="T1Q-RS-8o6" secondAttribute="bottom" constant="4" id="2DB-H2-E2v"/>
<constraint firstAttribute="bottom" secondItem="jta-3V-4wL" secondAttribute="bottom" id="3rt-Ig-1rG"/>
@ -108,7 +107,7 @@
<constraint firstItem="T1Q-RS-8o6" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="10" id="cc7-bg-15Z"/>
<constraint firstItem="Jkz-Zp-aaG" firstAttribute="leading" secondItem="eCk-zY-LXq" secondAttribute="leading" constant="7" id="fPh-Lb-Bv9"/>
<constraint firstItem="xws-BR-H47" firstAttribute="centerY" secondItem="T1Q-RS-8o6" secondAttribute="centerY" id="faX-hg-WfP"/>
<constraint firstItem="5Yd-df-HbB" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="55" id="h4R-2d-Xxn"/>
<constraint firstItem="5Yd-df-HbB" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="50" id="h4R-2d-Xxn"/>
<constraint firstAttribute="trailing" secondItem="oxX-IL-dG4" secondAttribute="trailing" constant="4" id="hDl-X9-M4n"/>
<constraint firstItem="T1Q-RS-8o6" firstAttribute="centerX" secondItem="eCk-zY-LXq" secondAttribute="centerX" id="hmB-fl-oN2"/>
<constraint firstItem="Q6g-b0-3sZ" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="5" id="jST-Ic-lsn"/>

View file

@ -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.
*/

View file

@ -62,28 +62,36 @@ 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;
}
if (event.eventType == MXEventTypeRoomCreate)
{
MXRoomCreateContent *createContent = [MXRoomCreateContent modelFromJSON:event.content];
if (createContent.roomPredecessorInfo)
case MXEventTypeRoomMember:
{
self.tag = RoomBubbleCellDataTagRoomCreateWithPredecessor;
// 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;
}
[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<MXKRoomBubbleCellDataStoring>)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

View file

@ -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

View file

@ -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 {

View file

@ -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 <MXKDataSourceDelegate>
- (void)roomDataSource:(RoomDataSource*)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel;
@end

View file

@ -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 deviceVerificationTransactionDidChangeNotificationObserver;
// Timer used to debounce cells refresh
@property (nonatomic, strong) NSTimer *refreshCellsTimer;
@property (nonatomic, readonly) id<RoomDataSourceDelegate> roomDataSourceDelegate;
@property(nonatomic, readwrite) RoomEncryptionTrustLevel encryptionTrustLevel;
@end
@implementation RoomDataSource
@ -71,6 +84,12 @@
[self reload];
}];
[self registerKeyVerificationRequestNotification];
[self registerDeviceVerificationTransactionNotification];
[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>)roomDataSourceDelegate
{
if (!self.delegate || ![self.delegate conformsToProtocol:@protocol(RoomDataSourceDelegate)])
{
return nil;
}
return ((id<RoomDataSourceDelegate>)(self.delegate));
}
- (void)updateEventFormatter
@ -117,6 +151,16 @@
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
if (self.keyVerificationRequestDidChangeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:self.keyVerificationRequestDidChangeNotificationObserver];
}
if (self.deviceVerificationTransactionDidChangeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:self.deviceVerificationTransactionDidChangeNotificationObserver];
}
[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
@ -190,6 +259,8 @@
roomBubbleCellData.senderAvatarPlaceholder = [AvatarGenerator generateAvatarForMatrixItem:roomBubbleCellData.senderId withDisplayName:roomBubbleCellData.senderDisplayName];
}
[self updateKeyVerificationIfNeededForRoomBubbleCellData:roomBubbleCellData];
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
// Finalize cell view customization here
@ -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<BubbleCellReadReceiptsDisplayable> readReceiptsDisplayable = (id<BubbleCellReadReceiptsDisplayable>)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<MXKRoomBubbleCellDataStoring> 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)registerDeviceVerificationTransactionNotification
{
self.deviceVerificationTransactionDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXDeviceVerificationTransactionDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification)
{
MXDeviceVerificationTransaction *deviceVerificationTransaction = (MXDeviceVerificationTransaction*)notification.object;
if ([deviceVerificationTransaction.dmRoomId isEqualToString:self.roomId])
{
RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:deviceVerificationTransaction.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.deviceVerificationManager 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

View file

@ -18,18 +18,6 @@
#import "DeviceTableViewCell.h"
@interface RoomMemberDetailsViewController : MXKRoomMemberDetailsViewController <UIGestureRecognizerDelegate, DeviceTableViewCellDelegate>
@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

View file

@ -28,15 +28,19 @@
#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 () <RoomMemberTitleViewDelegate, DeviceVerificationCoordinatorBridgePresenterDelegate>
@interface RoomMemberDetailsViewController () <UIGestureRecognizerDelegate, DeviceTableViewCellDelegate, RoomMemberTitleViewDelegate, DeviceVerificationCoordinatorBridgePresenterDelegate>
{
RoomMemberTitleView* memberTitleView;
NSInteger securityIndex;
NSArray<NSNumber*> *securityActionsArray;
/**
List of the admin actions on this member.
*/
@ -78,6 +82,26 @@
*/
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(nonatomic) UserEncryptionTrustLevel encryptionTrustLevel;
@property(nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter;
@end
@implementation RoomMemberDetailsViewController
@ -105,6 +129,7 @@
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
self.encryptionTrustLevel = UserEncryptionTrustLevelUnknown;
adminActionsArray = [[NSMutableArray alloc] init];
otherActionsArray = [[NSMutableArray alloc] init];
@ -195,10 +220,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) {
@ -262,6 +292,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
@ -382,14 +414,18 @@
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 = self.userEncryptionBadgeImage;
// Retrieve the existing direct chats
[directChatsArray removeAllObjects];
NSArray *directRoomIds = self.mainSession.directRooms[self.mxRoomMember.userId];
@ -403,39 +439,103 @@
}
// Retrieve member's devices
NSString *userId = self.mxRoomMember.userId;
__weak typeof(self) weakSelf = self;
[self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap) {
if (!RiotSettings.shared.enableCrossSigning)
{
[self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap, NSDictionary<NSString *,MXCrossSigningInfo *> *crossSigningKeysMap) {
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];
}
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) {
} 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];
}
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];
}
- (UIImage*)userEncryptionBadgeImage
{
NSString *encryptionIconName;
UIImage *encryptionIcon;
UserEncryptionTrustLevel userEncryptionTrustLevel = self.encryptionTrustLevel;
switch (userEncryptionTrustLevel) {
case UserEncryptionTrustLevelWarning:
encryptionIconName = @"encryption_warning";
break;
case UserEncryptionTrustLevelNormal:
encryptionIconName = @"encryption_normal";
break;
case UserEncryptionTrustLevelTrusted:
encryptionIconName = @"encryption_trusted";
break;
default:
break;
}
if (encryptionIconName)
{
encryptionIcon = [UIImage imageNamed:encryptionIconName];
}
return encryptionIcon;
}
- (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 +584,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 +718,31 @@
}
}
adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1;
if (RiotSettings.shared.enableCrossSigning)
{
switch (self.encryptionTrustLevel) {
case UserEncryptionTrustLevelUnknown:
securityActionsArray = @[@(MXKRoomMemberDetailsActionSecurityInformation)];
break;
case UserEncryptionTrustLevelNone:
case UserEncryptionTrustLevelNormal:
case UserEncryptionTrustLevelTrusted:
case UserEncryptionTrustLevelWarning:
securityActionsArray = @[@(MXKRoomMemberDetailsActionSecurity),
@(MXKRoomMemberDetailsActionSecurityInformation)];
break;
default:
break;
}
}
securityIndex = adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1;
if (securityActionsArray.count)
{
securityIndex = sectionCount++;
}
if (otherActionsArray.count)
{
@ -644,7 +768,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 +794,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 (RiotSettings.shared.enableCrossSigning && 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 +877,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 = self.userEncryptionBadgeImage;
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,26 +1068,9 @@
}
}
- (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)
if (!RiotSettings.shared.enableCrossSigning && section == otherActionsIndex)
{
return TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN;
}
@ -881,7 +1080,18 @@
- (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)
{

View file

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -15,6 +14,7 @@
<outlet property="bottomImageView" destination="7Dc-jk-9sT" id="BVN-bt-VXI"/>
<outlet property="memberHeaderView" destination="YXr-As-Mqh" id="Eqb-qr-iAo"/>
<outlet property="memberThumbnail" destination="GQ1-rP-ckr" id="abr-hr-C3p"/>
<outlet property="roomMemberAvatarBadgeImageView" destination="jHh-A3-In3" id="LKN-mv-WFg"/>
<outlet property="roomMemberAvatarHeaderBackground" destination="ouj-VM-zdT" id="YeD-zt-8y5"/>
<outlet property="roomMemberAvatarHeaderBackgroundHeightConstraint" destination="dBL-G6-Yec" id="QXZ-ZP-0Rn"/>
<outlet property="roomMemberAvatarMask" destination="MAS-3M-3cg" id="nLI-7d-5Hu"/>
@ -45,7 +45,7 @@
<rect key="frame" x="137.5" y="0.0" width="100" height="125"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GQ1-rP-ckr" customClass="MXKImageView">
<rect key="frame" x="7.5" y="31" width="84" height="84"/>
<rect key="frame" x="8" y="31" width="84" height="84"/>
<color key="backgroundColor" red="0.6886889638" green="1" blue="0.74383144840000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="MemberAvatar"/>
<constraints>
@ -53,11 +53,21 @@
<constraint firstAttribute="width" secondItem="GQ1-rP-ckr" secondAttribute="height" multiplier="1:1" id="a1T-Y0-Iic"/>
</constraints>
</view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="jHh-A3-In3">
<rect key="frame" x="68" y="91" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="jHh-A3-In3" secondAttribute="height" multiplier="1:1" id="fvP-Hk-apc"/>
<constraint firstAttribute="width" constant="24" id="gBW-ym-4Qv"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCAvatarMask"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="GQ1-rP-ckr" secondAttribute="bottom" constant="10" id="3pC-So-WvO"/>
<constraint firstItem="jHh-A3-In3" firstAttribute="bottom" secondItem="GQ1-rP-ckr" secondAttribute="bottom" id="6Gg-lp-pJw"/>
<constraint firstItem="jHh-A3-In3" firstAttribute="trailing" secondItem="GQ1-rP-ckr" secondAttribute="trailing" id="TbA-vY-3Ef"/>
<constraint firstItem="jHh-A3-In3" firstAttribute="width" secondItem="jHh-A3-In3" secondAttribute="height" multiplier="1:1" id="Ua2-xg-Vd2"/>
<constraint firstItem="GQ1-rP-ckr" firstAttribute="centerX" secondItem="MAS-3M-3cg" secondAttribute="centerX" id="ZGI-nR-gGx"/>
<constraint firstAttribute="width" constant="100" id="fwv-qE-IV1"/>
</constraints>

View file

@ -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
};

View file

@ -125,7 +125,8 @@
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate>
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;
@ -1413,6 +1422,11 @@
return NO;
}
- (BOOL)isEncryptionEnabled
{
return self.roomDataSource.room.summary.isEncrypted && self.mainSession.crypto != nil;
}
- (void)refreshRoomTitle
{
if (rightBarButtonItems && !self.navigationItem.rightBarButtonItems)
@ -1533,12 +1547,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 +1628,64 @@
[UIView setAnimationsEnabled:YES];
}
- (UIImage*)roomEncryptionBadgeImage
{
NSString *encryptionIconName;
UIImage *encryptionIcon;
if (self.isEncryptionEnabled)
{
RoomEncryptionTrustLevel roomEncryptionTrustLevel = ((RoomDataSource*)self.roomDataSource).encryptionTrustLevel;
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;
default:
break;
}
}
if (encryptionIconName)
{
encryptionIcon = [UIImage imageNamed:encryptionIconName];
}
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)updateEncryptionDecorationForRoomInputToolbar:(RoomInputToolbarView*)roomInputToolbarView
{
roomInputToolbarView.isEncryptionEnabled = self.isEncryptionEnabled;
roomInputToolbarView.encryptedRoomIcon.image = self.roomEncryptionBadgeImage;
}
- (void)handleLongPressFromCell:(id<MXKCellRendering>)cell withTappedEvent:(MXEvent*)event
{
if (event && !customizedRoomDataSource.selectedEventId)
@ -1717,9 +1785,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 +1821,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];
@ -2039,6 +2111,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)
@ -2255,6 +2339,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 +3257,14 @@
return roomInputToolbarView;
}
#pragma mark - RoomDataSourceDelegate
- (void)roomDataSource:(RoomDataSource *)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel
{
[self updateInputToolbarEncryptionDecoration];
[self updateExpandedHeaderEncryptionDecoration];
}
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
@ -5303,8 +5419,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);

View file

@ -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()
}
}

View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="zG5-YA-Ijy" customClass="BubbleCellContentView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XQw-Mj-NZY">
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="5GX-gn-bK1">
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
<subviews>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="u1e-Q2-PhY">
<rect key="frame" x="0.0" y="0.0" width="595" height="54"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ro1-vP-6Ha" userLabel="Pagination Title View">
<rect key="frame" x="67" y="10" width="518" height="24"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="r7y-FK-GWS" userLabel="Pagination Label">
<rect key="frame" x="0.0" y="0.0" width="508" height="18"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="uCj-An-Yc2"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<color key="textColor" red="0.66666666669999997" green="0.66666666669999997" blue="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ytv-tA-NmI" userLabel="Pagination Separator View">
<rect key="frame" x="0.0" y="23" width="518" height="1"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<accessibility key="accessibilityConfiguration" identifier="PaginationTitleView"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="5bk-I2-Cw6"/>
<constraint firstAttribute="bottom" secondItem="ytv-tA-NmI" secondAttribute="bottom" id="5oD-hl-YNI"/>
<constraint firstAttribute="trailing" secondItem="ytv-tA-NmI" secondAttribute="trailing" id="Cfu-Jn-urV"/>
<constraint firstItem="r7y-FK-GWS" firstAttribute="leading" secondItem="Ro1-vP-6Ha" secondAttribute="leading" id="Fpo-9J-9Ci"/>
<constraint firstItem="r7y-FK-GWS" firstAttribute="top" secondItem="Ro1-vP-6Ha" secondAttribute="top" id="Rpc-oi-muy"/>
<constraint firstItem="ytv-tA-NmI" firstAttribute="top" secondItem="r7y-FK-GWS" secondAttribute="bottom" constant="5" id="lam-eF-rEV"/>
<constraint firstItem="ytv-tA-NmI" firstAttribute="leading" secondItem="Ro1-vP-6Ha" secondAttribute="leading" id="oBH-4x-jgt"/>
<constraint firstAttribute="trailing" secondItem="r7y-FK-GWS" secondAttribute="trailing" constant="10" id="r4y-XW-aAD"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Ro1-vP-6Ha" firstAttribute="top" secondItem="u1e-Q2-PhY" secondAttribute="top" constant="10" id="5kZ-rt-pvq"/>
<constraint firstAttribute="trailing" secondItem="Ro1-vP-6Ha" secondAttribute="trailing" constant="10" id="8P7-ZL-7pg"/>
<constraint firstItem="Ro1-vP-6Ha" firstAttribute="leading" secondItem="u1e-Q2-PhY" secondAttribute="leading" constant="67" id="NCS-nK-fLb"/>
<constraint firstAttribute="bottom" secondItem="Ro1-vP-6Ha" secondAttribute="bottom" constant="20" id="UcW-P4-rQv"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vcq-cR-uBc">
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="oeI-eO-mFK">
<rect key="frame" x="56" y="3" width="474" height="91"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Y6-Py-paB">
<rect key="frame" x="530" y="3" width="50" height="91"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="BubbleInfoContainer"/>
<constraints>
<constraint firstAttribute="width" constant="50" id="LtA-zk-OCc"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="oeI-eO-mFK" firstAttribute="leading" secondItem="vcq-cR-uBc" secondAttribute="leading" constant="56" id="0Fr-0L-9tU"/>
<constraint firstItem="7Y6-Py-paB" firstAttribute="top" secondItem="vcq-cR-uBc" secondAttribute="top" constant="3" id="16j-F9-tL8"/>
<constraint firstAttribute="bottom" secondItem="oeI-eO-mFK" secondAttribute="bottom" constant="3" id="8M5-uW-82s"/>
<constraint firstItem="7Y6-Py-paB" firstAttribute="leading" secondItem="oeI-eO-mFK" secondAttribute="trailing" id="9V6-A8-9i0"/>
<constraint firstAttribute="bottom" secondItem="7Y6-Py-paB" secondAttribute="bottom" constant="3" id="lee-yN-381"/>
<constraint firstAttribute="trailing" secondItem="7Y6-Py-paB" secondAttribute="trailing" constant="15" id="rG0-0L-I4O"/>
<constraint firstItem="oeI-eO-mFK" firstAttribute="top" secondItem="vcq-cR-uBc" secondAttribute="top" constant="3" id="uZZ-I6-Xtq"/>
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4zo-V8-CNe">
<rect key="frame" x="0.0" y="0.0" width="595" height="12"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rQt-NH-Cb0">
<rect key="frame" x="439" y="0.0" width="150" height="12"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="fsY-DK-hUg"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="rQt-NH-Cb0" secondAttribute="bottom" id="6yV-vn-doj"/>
<constraint firstItem="rQt-NH-Cb0" firstAttribute="top" secondItem="4zo-V8-CNe" secondAttribute="top" id="DNc-jy-Urh"/>
<constraint firstAttribute="height" constant="12" id="TxZ-dJ-UI6"/>
<constraint firstAttribute="trailing" secondItem="rQt-NH-Cb0" secondAttribute="trailing" constant="6" id="lq2-AY-Lus"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="u1e-Q2-PhY" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="KrJ-dm-TaV"/>
<constraint firstItem="4zo-V8-CNe" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="bdq-sQ-NQy"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" red="0.95294117649999999" green="0.97254901959999995" blue="0.99215686270000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="5GX-gn-bK1" firstAttribute="leading" secondItem="zG5-YA-Ijy" secondAttribute="leading" id="36u-ut-l3G"/>
<constraint firstItem="5GX-gn-bK1" firstAttribute="top" secondItem="zG5-YA-Ijy" secondAttribute="top" id="As1-dI-9ba"/>
<constraint firstItem="XQw-Mj-NZY" firstAttribute="top" secondItem="zG5-YA-Ijy" secondAttribute="top" id="BuL-ri-8kT"/>
<constraint firstAttribute="bottom" secondItem="5GX-gn-bK1" secondAttribute="bottom" id="cNV-Or-YPg"/>
<constraint firstAttribute="trailing" secondItem="5GX-gn-bK1" secondAttribute="trailing" id="deR-Cu-Brh"/>
<constraint firstAttribute="trailing" secondItem="XQw-Mj-NZY" secondAttribute="trailing" id="eJl-Fg-neU"/>
<constraint firstAttribute="bottom" secondItem="XQw-Mj-NZY" secondAttribute="bottom" id="kPs-G8-HdC"/>
<constraint firstItem="XQw-Mj-NZY" firstAttribute="leading" secondItem="zG5-YA-Ijy" secondAttribute="leading" id="w8M-8g-ZQD"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="bubbleInfoContainer" destination="7Y6-Py-paB" id="uLv-MM-HIL"/>
<outlet property="bubbleInfoContainerTopConstraint" destination="16j-F9-tL8" id="zxd-pd-SSx"/>
<outlet property="bubbleOverlayContainer" destination="XQw-Mj-NZY" id="6d1-EN-LPY"/>
<outlet property="innerContentView" destination="oeI-eO-mFK" id="ap1-He-C6g"/>
<outlet property="paginationLabel" destination="r7y-FK-GWS" id="R9V-ix-mDu"/>
<outlet property="paginationSeparatorView" destination="ytv-tA-NmI" id="sgk-n1-KQi"/>
<outlet property="paginationTitleContainerView" destination="u1e-Q2-PhY" id="Osl-dF-fpA"/>
<outlet property="readReceiptsContainerView" destination="4zo-V8-CNe" id="7ek-u4-CX8"/>
<outlet property="readReceiptsContentView" destination="rQt-NH-Cb0" id="tqw-je-kp9"/>
</connections>
<point key="canvasLocation" x="-975" y="-1318"/>
</view>
</objects>
</document>

View file

@ -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()
}

View file

@ -23,60 +23,51 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt
+ (UIImage*)encryptionIconForEvent:(MXEvent*)event andSession:(MXSession*)session
{
NSString *encryptionIcon;
MXRoom *room = [session roomWithRoomId:event.roomId];
BOOL isRoomEncrypted = room.summary.isEncrypted && session.crypto;
if (!isRoomEncrypted)
{
return nil;
}
NSString *encryptionIconName;
UIImage* encryptionIcon;
if (!event.isEncrypted)
{
encryptionIcon = @"e2e_unencrypted";
if (event.isLocalEvent
|| event.isState
|| 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";
}
encryptionIconName = nil;
}
else
{
encryptionIconName = @"encryption_warning";
}
}
else if (event.decryptionError)
{
encryptionIcon = @"e2e_blocked";
encryptionIconName = @"encryption_warning";
}
else
else if (event.sender)
{
MXUserTrustLevel *userTrustLevel = [session.crypto trustLevelForUser:event.sender];
MXDeviceInfo *deviceInfo = [session.crypto eventDeviceInfo:event];
if (deviceInfo)
if (userTrustLevel.isVerified && !deviceInfo.trustLevel.isVerified)
{
switch (deviceInfo.verified)
{
case MXDeviceUnknown:
case MXDeviceUnverified:
{
encryptionIcon = @"e2e_warning";
break;
}
case MXDeviceVerified:
{
encryptionIcon = @"e2e_verified";
break;
}
default:
break;
}
encryptionIconName = @"encryption_warning";
}
}
if (!encryptionIcon)
if (encryptionIconName)
{
// Use the warning icon by default
encryptionIcon = @"e2e_warning";
encryptionIcon = [UIImage imageNamed:encryptionIconName];
}
return [UIImage imageNamed:encryptionIcon];
return encryptionIcon;
}
+ (void)addEncryptionStatusFromBubbleData:(MXKRoomBubbleCellData *)bubbleData inContainerView:(UIView *)containerView
@ -104,19 +95,23 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt
}
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;
if (icon)
{
UIImageView *encryptStatusImageView = [[UIImageView alloc] initWithImage:icon];
CGPoint center = encryptStatusImageView.center;
center.x = containerView.frame.size.width / 2;
encryptStatusImageView.center = center;
CGRect frame = encryptStatusImageView.frame;
frame.origin.y = component.position.y + 3;
encryptStatusImageView.frame = frame;
encryptStatusImageView.tag = componentIndex;
CGPoint center = encryptStatusImageView.center;
center.x = containerView.frame.size.width / 2;
encryptStatusImageView.center = center;
[containerView addSubview:encryptStatusImageView];
encryptStatusImageView.tag = componentIndex;
[containerView addSubview:encryptStatusImageView];
}
}
}

View file

@ -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<SizingViewHeight>()
}
// 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()
}
}

View file

@ -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?()
}
}

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="KeyVerificationCellInnerContentView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="313" height="133"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="2Sr-GM-aAU">
<rect key="frame" x="10" y="10" width="293" height="113"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="5gD-pX-GFz">
<rect key="frame" x="70" y="0.0" width="153.5" height="16"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="encryption_normal" translatesAutoresizingMaskIntoConstraints="NO" id="EHJ-3L-OPJ">
<rect key="frame" x="0.0" y="0.0" width="16" height="16"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Verification request" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BOd-4B-LQX">
<rect key="frame" x="20" y="0.0" width="133.5" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="751" text="User (@user:matrix.org)" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e1n-WP-GTb">
<rect key="frame" x="83.5" y="26" width="126" height="13.5"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.1803921568627451" green="0.18431372549019609" blue="0.19607843137254902" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Waiting..." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e6I-aZ-RRO">
<rect key="frame" x="118" y="49.5" width="57" height="13.5"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" red="0.38039215686274508" green="0.4392156862745098" blue="0.54509803921568623" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5Tr-gP-gPB">
<rect key="frame" x="0.0" y="73" width="293" height="40"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="WxG-vh-Bn0">
<rect key="frame" x="71" y="0.0" width="151" height="40"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="atD-LF-sGH">
<rect key="frame" x="0.0" y="0.0" width="72" height="40"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="Decline">
<color key="titleColor" red="1" green="0.29411764705882354" blue="0.33333333333333331" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="declineButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="dS6-Xr-6jZ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="irs-8W-qcs">
<rect key="frame" x="82" y="0.0" width="69" height="40"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="Accept">
<color key="titleColor" red="0.011764705882352941" green="0.70196078431372544" blue="0.50588235294117645" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="acceptButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="IQ6-be-vJt"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="atD-LF-sGH" firstAttribute="height" secondItem="WxG-vh-Bn0" secondAttribute="height" priority="999" id="3sT-HV-Os0"/>
<constraint firstItem="irs-8W-qcs" firstAttribute="height" secondItem="WxG-vh-Bn0" secondAttribute="height" priority="999" id="CfM-Vc-MQ2"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="WxG-vh-Bn0" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="5Tr-gP-gPB" secondAttribute="leading" id="4al-oB-NpF"/>
<constraint firstAttribute="bottom" secondItem="WxG-vh-Bn0" secondAttribute="bottom" id="rCo-gP-UF5"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="WxG-vh-Bn0" secondAttribute="trailing" id="u47-KK-aKk"/>
<constraint firstAttribute="height" priority="999" constant="40" id="ujN-jh-fde"/>
<constraint firstItem="WxG-vh-Bn0" firstAttribute="top" secondItem="5Tr-gP-gPB" secondAttribute="top" id="wNH-CW-iLB"/>
<constraint firstItem="WxG-vh-Bn0" firstAttribute="centerX" secondItem="5Tr-gP-gPB" secondAttribute="centerX" id="yWq-qo-MY7"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="5Tr-gP-gPB" firstAttribute="width" secondItem="2Sr-GM-aAU" secondAttribute="width" id="VJx-xp-NZE"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" red="0.95294117647058818" green="0.97254901960784312" blue="0.99215686274509807" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="2Sr-GM-aAU" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="10" id="M7S-MY-Jxk"/>
<constraint firstItem="2Sr-GM-aAU" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="10" id="SfH-5W-p30"/>
<constraint firstAttribute="trailing" secondItem="2Sr-GM-aAU" secondAttribute="trailing" constant="10" id="rVw-1F-ch3"/>
<constraint firstAttribute="bottom" secondItem="2Sr-GM-aAU" secondAttribute="bottom" constant="10" id="vL2-dm-qbe"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="acceptButton" destination="irs-8W-qcs" id="LKq-qE-bbg"/>
<outlet property="badgeImageView" destination="EHJ-3L-OPJ" id="eNW-bg-eYy"/>
<outlet property="buttonsContainerView" destination="5Tr-gP-gPB" id="aqy-vT-mu3"/>
<outlet property="declineButton" destination="atD-LF-sGH" id="CfI-FW-ySy"/>
<outlet property="otherUserInformationLabel" destination="e1n-WP-GTb" id="jhY-gH-QnO"/>
<outlet property="requestStatusLabel" destination="e6I-aZ-RRO" id="zQf-qy-3Rq"/>
<outlet property="titleLabel" destination="BOd-4B-LQX" id="4sw-3J-faF"/>
</connections>
<point key="canvasLocation" x="-828.26086956521749" y="-386.04910714285711"/>
</view>
</objects>
<resources>
<image name="encryption_normal" width="16" height="16"/>
</resources>
</document>

View file

@ -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
}
}

View file

@ -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?
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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?
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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 <MediaPickerViewControllerDelegate>
@interface RoomInputToolbarView : MXKRoomInputToolbarViewWithHPGrowingText
/**
The delegate notified when inputs are ready.

View file

@ -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)
{

View file

@ -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;

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -27,11 +27,20 @@
<constraint firstAttribute="width" constant="84" id="Yye-G1-hCH"/>
</constraints>
</view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="14n-e5-ix3">
<rect key="frame" x="318" y="71" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="14n-e5-ix3" secondAttribute="height" multiplier="1:1" id="g2h-P2-8gQ"/>
<constraint firstAttribute="width" constant="24" id="oSg-O6-MRx"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="RoomAvatarHeaderBackground"/>
<constraints>
<constraint firstItem="14n-e5-ix3" firstAttribute="trailing" secondItem="eEk-n9-4dA" secondAttribute="trailing" id="3fH-JR-GgP"/>
<constraint firstAttribute="height" constant="95" id="DzD-aR-3eV"/>
<constraint firstItem="14n-e5-ix3" firstAttribute="bottom" secondItem="eEk-n9-4dA" secondAttribute="bottom" id="Or6-Ka-Yny"/>
<constraint firstItem="eEk-n9-4dA" firstAttribute="centerX" secondItem="uSp-YH-L18" secondAttribute="centerX" id="W1w-ZX-f2d"/>
<constraint firstAttribute="bottom" secondItem="eEk-n9-4dA" secondAttribute="bottom" id="hXd-bM-Bbb"/>
</constraints>
@ -148,6 +157,7 @@
<outlet property="displayNameTextField" destination="6uH-I3-RQg" id="MfX-LQ-C2K"/>
<outlet property="membersListIcon" destination="S3Y-wJ-HOe" id="hkf-a4-Ffw"/>
<outlet property="roomAvatar" destination="eEk-n9-4dA" id="5jd-Dx-tXE"/>
<outlet property="roomAvatarBadgeImageView" destination="14n-e5-ix3" id="ZSS-wz-Oi7"/>
<outlet property="roomAvatarHeaderBackground" destination="uSp-YH-L18" id="sfW-ED-5bD"/>
<outlet property="roomAvatarHeaderBackgroundHeightConstraint" destination="DzD-aR-3eV" id="SuC-mO-epX"/>
<outlet property="roomDetailsMask" destination="MFb-0F-eO8" id="ajK-sr-qf7"/>
@ -155,10 +165,11 @@
<outlet property="roomTopic" destination="qD3-kA-DSI" id="mOj-AU-7LM"/>
<outlet property="titleMask" destination="8HH-9b-1yH" id="MFh-3r-I5e"/>
</connections>
<point key="canvasLocation" x="129.59999999999999" y="153.37331334332833"/>
</view>
</objects>
<resources>
<image name="add_participant" width="92.800003051757812" height="92.800003051757812"/>
<image name="members_list_icon" width="15" height="15"/>
<image name="add_participant" width="30" height="30"/>
<image name="members_list_icon" width="15" height="12"/>
</resources>
</document>

View file

@ -151,10 +151,10 @@
deviceVerificationCoordinatorBridgePresenter = nil;
// Check device new status
[self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap) {
[self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap, NSDictionary<NSString *,MXCrossSigningInfo *> *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.

View file

@ -248,13 +248,17 @@ private enum BackupRows {
if device.fingerprint == self.userDevice.fingerprint {
return VectorL10n.settingsKeyBackupInfoTrustSignatureValid
} else if signature.valid && (device.verified == MXDeviceVerified) {
} else if signature.valid
&& (device.trustLevel.localVerificationStatus == .verified) {
return VectorL10n.settingsKeyBackupInfoTrustSignatureValidDeviceVerified(displayName)
} else if signature.valid && (device.verified != MXDeviceVerified) {
} else if signature.valid
&& (device.trustLevel.localVerificationStatus != .verified) {
return VectorL10n.settingsKeyBackupInfoTrustSignatureValidDeviceUnverified(displayName)
} else if !signature.valid && (device.verified == MXDeviceVerified) {
} else if !signature.valid
&& (device.trustLevel.localVerificationStatus == .verified) {
return VectorL10n.settingsKeyBackupInfoTrustSignatureInvalidDeviceVerified(displayName)
} else if !signature.valid && (device.verified != MXDeviceVerified) {
} else if !signature.valid
&& (device.trustLevel.localVerificationStatus != .verified) {
return VectorL10n.settingsKeyBackupInfoTrustSignatureInvalidDeviceUnverified(displayName)
}

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="QHp-lE-M7m">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Manage Session View Controller-->
<scene sceneID="KQ6-RY-1vS">
<objects>
<tableViewController id="QHp-lE-M7m" customClass="ManageSessionViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Fpr-At-4Wb">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="JHA-Ac-OAZ">
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JHA-Ac-OAZ" id="Soh-58-aeC">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="QHp-lE-M7m" id="F5e-nl-xhe"/>
<outlet property="delegate" destination="QHp-lE-M7m" id="TJn-MA-abi"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Zo3-gk-o9e" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="65" y="68"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,25 @@
/*
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 <MatrixKit/MatrixKit.h>
@interface ManageSessionViewController : MXKTableViewController
+ (ManageSessionViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession andDevice:(MXDevice*)device;
@end

View file

@ -0,0 +1,743 @@
/*
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 "ManageSessionViewController.h"
#import <MatrixKit/MatrixKit.h>
#import <OLMKit/OLMKit.h>
#import "AppDelegate.h"
#import "AvatarGenerator.h"
#import "ThemeService.h"
#import "Riot-Swift.h"
enum
{
SECTION_SESSION_INFO,
SECTION_ACTION,
SECTION_COUNT
};
enum {
SESSION_INFO_SESSION_NAME,
SESSION_INFO_TRUST,
SESSION_INFO_COUNT
};
enum {
ACTION_REMOVE_SESSION,
ACTION_COUNT
};
@interface ManageSessionViewController ()
{
// The device to display
MXDevice *device;
// Current alert (if any).
UIAlertController *currentAlert;
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
// The current pushed view controller
UIViewController *pushedViewController;
}
@property (nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter;
@end
@implementation ManageSessionViewController
#pragma mark - Setup & Teardown
+ (ManageSessionViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession andDevice:(MXDevice*)device;
{
ManageSessionViewController* viewController = [[UIStoryboard storyboardWithName:@"ManageSession" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
[viewController addMatrixSession:matrixSession];
viewController->device = device;
return viewController;
}
#pragma mark - View life cycle
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.title = NSLocalizedStringFromTable(@"manage_session_title", @"Vector", nil);
// Remove back bar button title when pushing a view controller
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier]];
[self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]];
[self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]];
// Enable self sizing cells
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 50;
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
}
- (void)userInterfaceThemeDidChange
{
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
// 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);
self.view.backgroundColor = self.tableView.backgroundColor;
self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
[self reloadData];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.shared.theme.statusBarStyle;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)destroy
{
// Release the potential pushed view controller
[self releasePushedViewController];
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"ManageSession"];
// Release the potential pushed view controller
[self releasePushedViewController];
// Refresh display
[self reloadData];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (currentAlert)
{
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = nil;
}
}
#pragma mark - Internal methods
- (void)pushViewController:(UIViewController*)viewController
{
// Keep ref on pushed view controller
pushedViewController = viewController;
// Hide back button title
self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.navigationController pushViewController:viewController animated:YES];
}
- (void)releasePushedViewController
{
if (pushedViewController)
{
if ([pushedViewController isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController*)pushedViewController;
for (id subViewController in navigationController.viewControllers)
{
if ([subViewController respondsToSelector:@selector(destroy)])
{
[subViewController destroy];
}
}
}
else if ([pushedViewController respondsToSelector:@selector(destroy)])
{
[(id)pushedViewController destroy];
}
pushedViewController = nil;
}
}
- (void)reset
{
// Remove observers
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)reloadData
{
// Trigger a full table reloadData
[self.tableView reloadData];
}
- (void)reloadDeviceWithCompletion:(void (^)(void))completion
{
MXWeakify(self);
[self.mainSession.matrixRestClient deviceByDeviceId:device.deviceId success:^(MXDevice *device) {
MXStrongifyAndReturnIfNil(self);
self->device = device;
[self reloadData];
completion();
} failure:^(NSError *error) {
NSLog(@"[ManageSessionVC] reloadDeviceWithCompletion failed. Error: %@", error);
[self reloadData];
completion();
}];
}
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Keep ref on destinationViewController
[super prepareForSegue:segue sender:sender];
// FIXME add night mode
}
#pragma mark - UITableView data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return SECTION_COUNT;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
switch (section)
{
case SECTION_SESSION_INFO:
count = SESSION_INFO_COUNT;
break;
case SECTION_ACTION:
count = ACTION_COUNT;
break;
}
return count;
}
- (MXKTableViewCellWithLabelAndTextField*)getLabelAndTextFieldCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath
{
MXKTableViewCellWithLabelAndTextField *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier] forIndexPath:indexPath];
cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left;
cell.mxkTextFieldLeadingConstraint.constant = 16;
cell.mxkTextFieldTrailingConstraint.constant = 15;
cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
cell.mxkTextField.userInteractionEnabled = YES;
cell.mxkTextField.borderStyle = UITextBorderStyleNone;
cell.mxkTextField.textAlignment = NSTextAlignmentRight;
cell.mxkTextField.textColor = ThemeService.shared.theme.textSecondaryColor;
cell.mxkTextField.font = [UIFont systemFontOfSize:16];
cell.mxkTextField.placeholder = nil;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = nil;
cell.alpha = 1.0f;
cell.userInteractionEnabled = YES;
[cell layoutIfNeeded];
return cell;
}
- (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath
{
MXKTableViewCellWithLabelAndSwitch *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath];
cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left;
cell.mxkSwitchTrailingConstraint.constant = 15;
cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
[cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
// Force layout before reusing a cell (fix switch displayed outside the screen)
[cell layoutIfNeeded];
return cell;
}
- (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView
{
MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]];
if (!cell)
{
cell = [[MXKTableViewCell alloc] init];
}
else
{
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = nil;
cell.imageView.image = nil;
}
cell.textLabel.accessibilityIdentifier = nil;
cell.textLabel.font = [UIFont systemFontOfSize:17];
cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
cell.contentView.backgroundColor = UIColor.clearColor;
return cell;
}
- (MXKTableViewCell*)trustCellWithDevice:(MXDevice*)device forTableView:(UITableView*)tableView
{
MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView];
NSString *deviceId = device.deviceId;
MXDeviceInfo *deviceInfo = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId];
cell.textLabel.numberOfLines = 0;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
if (deviceInfo.trustLevel.isVerified)
{
cell.textLabel.text = NSLocalizedStringFromTable(@"manage_session_trusted", @"Vector", nil);
cell.imageView.image = [UIImage imageNamed:@"encryption_trusted"];
}
else
{
cell.textLabel.text = NSLocalizedStringFromTable(@"manage_session_not_trusted", @"Vector", nil);
cell.imageView.image = [UIImage imageNamed:@"encryption_warning"];
}
return cell;
}
- (MXKTableViewCell*)descriptionCellForTableView:(UITableView*)tableView withText:(NSString*)text
{
MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView];
cell.textLabel.text = text;
cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
cell.textLabel.numberOfLines = 0;
cell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
- (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath
{
MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath];
textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor;
textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17];
textViewCell.mxkTextView.backgroundColor = [UIColor clearColor];
textViewCell.mxkTextViewLeadingConstraint.constant = tableView.separatorInset.left;
textViewCell.mxkTextViewTrailingConstraint.constant = tableView.separatorInset.right;
textViewCell.mxkTextView.accessibilityIdentifier = nil;
return textViewCell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
// set the cell to a default value to avoid application crashes
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.backgroundColor = [UIColor redColor];
switch (section)
{
case SECTION_SESSION_INFO:
switch (row)
{
case SESSION_INFO_SESSION_NAME:
{
MXKTableViewCellWithLabelAndTextField *displaynameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath];
displaynameCell.mxkLabel.text = NSLocalizedStringFromTable(@"manage_session_name", @"Vector", nil);
displaynameCell.mxkTextField.text = device.displayName;
displaynameCell.mxkTextField.userInteractionEnabled = NO;
displaynameCell.selectionStyle = UITableViewCellSelectionStyleDefault;
cell = displaynameCell;
break;
}
case SESSION_INFO_TRUST:
{
cell = [self trustCellWithDevice:device forTableView:tableView];
}
}
break;
case SECTION_ACTION:
switch (row)
{
case ACTION_REMOVE_SESSION:
{
MXKTableViewCellWithButton *removeSessionBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!removeSessionBtnCell)
{
removeSessionBtnCell = [[MXKTableViewCellWithButton alloc] init];
}
else
{
// Fix https://github.com/vector-im/riot-ios/issues/1354
removeSessionBtnCell.mxkButton.titleLabel.text = nil;
}
NSString *btnTitle = NSLocalizedStringFromTable(@"manage_session_sign_out", @"Vector", nil);
[removeSessionBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
[removeSessionBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
[removeSessionBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor];
removeSessionBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
removeSessionBtnCell.mxkButton.userInteractionEnabled = NO;
removeSessionBtnCell.selectionStyle = UITableViewCellSelectionStyleDefault;
cell = removeSessionBtnCell;
break;
}
}
break;
}
return cell;
}
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
switch (section)
{
case SECTION_SESSION_INFO:
return NSLocalizedStringFromTable(@"manage_session_info", @"Vector", nil);
case SECTION_ACTION:
return @"";
}
return nil;
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
if ([view isKindOfClass:UITableViewHeaderFooterView.class])
{
// Customize label style
UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view;
tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
tableViewHeaderFooterView.textLabel.font = [UIFont systemFontOfSize:15];
}
}
#pragma mark - UITableView delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
if (cell.selectionStyle != UITableViewCellSelectionStyleNone)
{
// Update the selected background view
if (ThemeService.shared.theme.selectedBackgroundColor)
{
cell.selectedBackgroundView = [[UIView alloc] init];
cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor;
}
else
{
if (tableView.style == UITableViewStylePlain)
{
cell.selectedBackgroundView = nil;
}
else
{
cell.selectedBackgroundView.backgroundColor = nil;
}
}
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == SECTION_SESSION_INFO)
{
return 44;
}
return 24;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
if (section == SECTION_SESSION_INFO)
{
return 0;
}
return 24;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.tableView == tableView)
{
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
switch (section)
{
case SECTION_SESSION_INFO:
switch (row)
{
case SESSION_INFO_SESSION_NAME:
[self renameDevice];
break;
case SESSION_INFO_TRUST:
[self showTrustForDevice:device];
break;
}
break;
case SECTION_ACTION:
{
switch (row)
{
case ACTION_REMOVE_SESSION:
[self removeDevice];
break;
}
}
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
#pragma mark - actions
- (void)renameDevice
{
// Prompt the user to enter a device name.
[currentAlert dismissViewControllerAnimated:NO completion:nil];
MXWeakify(self);
currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"device_details_rename_prompt_title"]
message:[NSBundle mxk_localizedStringForKey:@"device_details_rename_prompt_message"] preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
MXStrongifyAndReturnIfNil(self);
textField.secureTextEntry = NO;
textField.placeholder = nil;
textField.keyboardType = UIKeyboardTypeDefault;
textField.text = self->device.displayName;
}];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
MXStrongifyAndReturnIfNil(self);
NSString *text = [self->currentAlert textFields].firstObject.text;
self->currentAlert = nil;
// Hot change
self->device.displayName = text;
[self reloadData];
[self.activityIndicator startAnimating];
[self.mainSession.matrixRestClient setDeviceName:text forDeviceId:self->device.deviceId success:^{
[self reloadDeviceWithCompletion:^{
[self.activityIndicator stopAnimating];
}];
} failure:^(NSError *error) {
NSLog(@"[ManageSessionVC] Rename device (%@) failed", self->device.deviceId);
[self reloadDeviceWithCompletion:^{
[self.activityIndicator stopAnimating];
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}];
}]];
[self presentViewController:currentAlert animated:YES completion:nil];
}
- (void)showTrustForDevice:(MXDevice *)device
{
UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter = [[UserVerificationCoordinatorBridgePresenter alloc] initWithPresenter:self
session:self.mainSession
userId:self.mainSession.myUser.userId
userDisplayName:nil
deviceId:device.deviceId];
[userVerificationCoordinatorBridgePresenter start];
self.userVerificationCoordinatorBridgePresenter = userVerificationCoordinatorBridgePresenter;
}
- (void)removeDevice
{
// Get an authentication session to prepare device deletion
[self.activityIndicator startAnimating];
MXWeakify(self);
[self.mainSession.matrixRestClient getSessionToDeleteDeviceByDeviceId:device.deviceId success:^(MXAuthenticationSession *authSession) {
MXStrongifyAndReturnIfNil(self);
// Check whether the password based type is supported
BOOL isPasswordBasedTypeSupported = NO;
for (MXLoginFlow *loginFlow in authSession.flows)
{
if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword] || [loginFlow.stages indexOfObject:kMXLoginFlowTypePassword] != NSNotFound)
{
isPasswordBasedTypeSupported = YES;
break;
}
}
if (isPasswordBasedTypeSupported && authSession.session)
{
// Prompt for a password
[self->currentAlert dismissViewControllerAnimated:NO completion:nil];
// Prompt the user before deleting the device.
self->currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"device_details_delete_prompt_title"] message:[NSBundle mxk_localizedStringForKey:@"device_details_delete_prompt_message"] preferredStyle:UIAlertControllerStyleAlert];
[self->currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
textField.placeholder = nil;
textField.keyboardType = UIKeyboardTypeDefault;
}];
[self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
self->currentAlert = nil;
[self.activityIndicator stopAnimating];
}]];
[self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"submit"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
UITextField *textField = [self->currentAlert textFields].firstObject;
self->currentAlert = nil;
NSString *userId = self.mainSession.myUser.userId;
NSDictionary *authParams;
// Sanity check
if (userId)
{
authParams = @{@"session":authSession.session,
@"user": userId,
@"password": textField.text,
@"type": kMXLoginFlowTypePassword};
}
[self.mainSession.matrixRestClient deleteDeviceByDeviceId:self->device.deviceId authParams:authParams success:^{
[self.activityIndicator stopAnimating];
// We cannot stay in this screen anymore
[self withdrawViewControllerAnimated:YES completion:nil];
} failure:^(NSError *error) {
NSLog(@"[ManageSessionVC] Delete device (%@) failed", self->device.deviceId);
[self.activityIndicator stopAnimating];
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}]];
[self presentViewController:self->currentAlert animated:YES completion:nil];
}
else
{
NSLog(@"[ManageSessionVC] Delete device (%@) failed, auth session flow type is not supported", self->device.deviceId);
[self.activityIndicator stopAnimating];
//[[AppDelegate theDelegate] showErrorAsAlert:error];
}
} failure:^(NSError *error) {
NSLog(@"[ManageSessionVC] Delete device (%@) failed, unable to get auth session", self->device.deviceId);
[self.activityIndicator stopAnimating];
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}
@end

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="QHp-lE-M7m">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Security View Controller-->
<scene sceneID="KQ6-RY-1vS">
<objects>
<tableViewController id="QHp-lE-M7m" customClass="SecurityViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Fpr-At-4Wb">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="JHA-Ac-OAZ">
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JHA-Ac-OAZ" id="Soh-58-aeC">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="QHp-lE-M7m" id="F5e-nl-xhe"/>
<outlet property="delegate" destination="QHp-lE-M7m" id="TJn-MA-abi"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Zo3-gk-o9e" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="65" y="68"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,24 @@
/*
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 <MatrixKit/MatrixKit.h>
@interface SecurityViewController : MXKTableViewController
+ (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession;
@end

View file

@ -0,0 +1,994 @@
/*
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 "SecurityViewController.h"
#import "ManageSessionViewController.h"
#import <MatrixKit/MatrixKit.h>
#import <OLMKit/OLMKit.h>
#import "AppDelegate.h"
#import "AvatarGenerator.h"
#import "ThemeService.h"
#import "Riot-Swift.h"
enum
{
SECTION_CRYPTO_SESSIONS,
SECTION_KEYBACKUP,
SECTION_ADVANCED,
SECTION_DEBUG, // TODO: To remove
SECTION_COUNT
};
enum {
ADVANCED_BLACKLIST_UNVERIFIED_DEVICES,
ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION,
ADVANCED_EXPORT, // TODO: To move to SECTION_KEYBACKUP
ADVANCED_COUNT
};
enum {
DEBUG_CRYPTO_INFO,
DEBUG_CROSSSIGNING_INFO,
DEBUG_CROSSSIGNING_BOOTSTRAP,
DEBUG_COUNT
};
@interface SecurityViewController () <
SettingsKeyBackupTableViewSectionDelegate,
KeyBackupSetupCoordinatorBridgePresenterDelegate,
KeyBackupRecoverCoordinatorBridgePresenterDelegate,
UIDocumentInteractionControllerDelegate>
{
// Current alert (if any).
UIAlertController *currentAlert;
// Devices
NSMutableArray<MXDevice *> *devicesArray;
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
// The view used to export e2e keys
MXKEncryptionKeysExportView *exportView;
// The document interaction Controller used to export e2e keys
UIDocumentInteractionController *documentInteractionController;
NSURL *keyExportsFile;
NSTimer *keyExportsFileDeletionTimer;
// The current pushed view controller
UIViewController *pushedViewController;
SettingsKeyBackupTableViewSection *keyBackupSection;
KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter;
KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter;
}
@end
@implementation SecurityViewController
#pragma mark - Setup & Teardown
+ (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession
{
SecurityViewController* viewController = [[UIStoryboard storyboardWithName:@"Security" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
[viewController addMatrixSession:matrixSession];
return viewController;
}
#pragma mark - View life cycle
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.title = NSLocalizedStringFromTable(@"security_settings_title", @"Vector", nil);
// Remove back bar button title when pushing a view controller
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]];
[self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]];
// Enable self sizing cells
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 50;
if (self.mainSession.crypto.backup)
{
MXDeviceInfo *deviceInfo = [self.mainSession.crypto.deviceList storedDevice:self.mainSession.matrixRestClient.credentials.userId
deviceId:self.mainSession.matrixRestClient.credentials.deviceId];
if (deviceInfo)
{
keyBackupSection = [[SettingsKeyBackupTableViewSection alloc] initWithKeyBackup:self.mainSession.crypto.backup userDevice:deviceInfo];
keyBackupSection.delegate = self;
}
}
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
}
- (void)userInterfaceThemeDidChange
{
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
// 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);
self.view.backgroundColor = self.tableView.backgroundColor;
self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
[self reloadData];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.shared.theme.statusBarStyle;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)destroy
{
// Release the potential pushed view controller
[self releasePushedViewController];
if (documentInteractionController)
{
[documentInteractionController dismissPreviewAnimated:NO];
[documentInteractionController dismissMenuAnimated:NO];
documentInteractionController = nil;
}
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
keyBackupSetupCoordinatorBridgePresenter = nil;
keyBackupRecoverCoordinatorBridgePresenter = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"Security"];
// Release the potential pushed view controller
[self releasePushedViewController];
// Refresh display
[self reloadData];
// Refresh the current device information in parallel
[self loadCurrentDeviceInformation];
// Refresh devices in parallel
[self loadDevices];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (currentAlert)
{
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = nil;
}
}
#pragma mark - Internal methods
- (void)pushViewController:(UIViewController*)viewController
{
// Keep ref on pushed view controller
pushedViewController = viewController;
// Hide back button title
self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.navigationController pushViewController:viewController animated:YES];
}
- (void)releasePushedViewController
{
if (pushedViewController)
{
if ([pushedViewController isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController*)pushedViewController;
for (id subViewController in navigationController.viewControllers)
{
if ([subViewController respondsToSelector:@selector(destroy)])
{
[subViewController destroy];
}
}
}
else if ([pushedViewController respondsToSelector:@selector(destroy)])
{
[(id)pushedViewController destroy];
}
pushedViewController = nil;
}
}
- (void)reset
{
// Remove observers
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)loadCurrentDeviceInformation
{
// Refresh the current device information
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
[account loadDeviceInformation:^{
// Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self reloadData];
} failure:nil];
}
- (NSAttributedString*)cryptographyInformation
{
// TODO Handle multi accounts
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
// Crypto information
NSMutableAttributedString *cryptoInformationString = [[NSMutableAttributedString alloc]
initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_name", @"Vector", nil)
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:account.device.displayName ? account.device.displayName : @""
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_id", @"Vector", nil)
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:account.device.deviceId ? account.device.deviceId : @""
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_key", @"Vector", nil)
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
NSString *fingerprint = account.mxSession.crypto.deviceEd25519Key;
if (fingerprint)
{
fingerprint = [MXTools addWhiteSpacesToString:fingerprint every:4];
}
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:fingerprint ? fingerprint : @""
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]];
return cryptoInformationString;
}
- (NSAttributedString*)crossSigningStatus
{
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
MXCrossSigning *crossSigning = account.mxSession.crypto.crossSigning;
MXCrossSigningInfo *myUserCrossSigningKeys = crossSigning.myUserCrossSigningKeys;
// Crypto information
NSMutableAttributedString *cryptoInformationString = [NSMutableAttributedString new];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:@"Cross-Signing\n"
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]];
NSString *crossSigningEnabled = [NSString stringWithFormat:@"Cross-signing is %@.\n",
crossSigning.isBootstrapped ? @"enabled" :
myUserCrossSigningKeys ? @"enabled in read-only" : @"disabled"];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:crossSigningEnabled
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
NSString *crossSigningKeysTrust = [NSString stringWithFormat:@"Keys are %@.\n",
myUserCrossSigningKeys.trustLevel.isVerified ? @"trusted" : @"not trusted"];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:crossSigningKeysTrust
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
return cryptoInformationString;
}
- (void)loadDevices
{
// Refresh the account devices list
MXWeakify(self);
[self.mainSession.matrixRestClient devices:^(NSArray<MXDevice *> *devices) {
MXStrongifyAndReturnIfNil(self);
if (devices)
{
self->devicesArray = [NSMutableArray arrayWithArray:devices];
// Sort devices according to the last seen date.
NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) {
if (deviceA.lastSeenTs > deviceB.lastSeenTs)
{
return NSOrderedAscending;
}
if (deviceA.lastSeenTs < deviceB.lastSeenTs)
{
return NSOrderedDescending;
}
return NSOrderedSame;
};
// Sort devices list
[self->devicesArray sortUsingComparator:comparator];
}
else
{
self->devicesArray = nil;
}
// Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self reloadData];
} failure:^(NSError *error) {
// Display the data that has been loaded last time
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self reloadData];
}];
}
- (void)reloadData
{
// Trigger a full table reloadData
[self.tableView reloadData];
}
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Keep ref on destinationViewController
[super prepareForSegue:segue sender:sender];
// FIXME add night mode
}
#pragma mark - UITableView data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return SECTION_COUNT;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
switch (section)
{
case SECTION_CRYPTO_SESSIONS:
count = devicesArray.count + 1;
break;
case SECTION_KEYBACKUP:
count = keyBackupSection.numberOfRows;
break;
case SECTION_ADVANCED:
count = ADVANCED_COUNT;
break;
case SECTION_DEBUG:
count = DEBUG_COUNT;
break;
}
return count;
}
- (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath
{
MXKTableViewCellWithLabelAndSwitch *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath];
cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left;
cell.mxkSwitchTrailingConstraint.constant = 15;
cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
[cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
// Force layout before reusing a cell (fix switch displayed outside the screen)
[cell layoutIfNeeded];
return cell;
}
- (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView
{
MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]];
if (!cell)
{
cell = [[MXKTableViewCell alloc] init];
}
else
{
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = nil;
cell.imageView.image = nil;
}
cell.textLabel.accessibilityIdentifier = nil;
cell.textLabel.font = [UIFont systemFontOfSize:17];
cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
cell.contentView.backgroundColor = UIColor.clearColor;
return cell;
}
- (MXKTableViewCell*)deviceCellWithDevice:(MXDevice*)device forTableView:(UITableView*)tableView
{
MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView];
NSString *name = device.displayName;
NSString *deviceId = device.deviceId;
cell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]);
cell.textLabel.numberOfLines = 0;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId])
{
cell.textLabel.font = [UIFont boldSystemFontOfSize:17];
}
cell.imageView.image = [self shieldImageForDevice:deviceId];
return cell;
}
- (UIImage*)shieldImageForDevice:(NSString*)deviceId
{
UIImage* shieldImageForDevice = [UIImage imageNamed:@"encryption_warning"];
MXDeviceInfo *device = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId];
if (device.trustLevel.isVerified)
{
shieldImageForDevice = [UIImage imageNamed:@"encryption_trusted"];
}
return shieldImageForDevice;
}
- (MXKTableViewCell*)descriptionCellForTableView:(UITableView*)tableView withText:(NSString*)text
{
MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView];
cell.textLabel.text = text;
cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
cell.textLabel.numberOfLines = 0;
cell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
- (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath
{
MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath];
textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor;
textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17];
textViewCell.mxkTextView.backgroundColor = [UIColor clearColor];
textViewCell.mxkTextViewLeadingConstraint.constant = tableView.separatorInset.left;
textViewCell.mxkTextViewTrailingConstraint.constant = tableView.separatorInset.right;
textViewCell.mxkTextView.accessibilityIdentifier = nil;
return textViewCell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
// set the cell to a default value to avoid application crashes
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.backgroundColor = [UIColor redColor];
MXSession* session = self.mainSession;
if (section == SECTION_CRYPTO_SESSIONS)
{
if (row < devicesArray.count)
{
cell = [self deviceCellWithDevice:devicesArray[row] forTableView:tableView];
}
else if (row == devicesArray.count)
{
cell = [self descriptionCellForTableView:tableView
withText:NSLocalizedStringFromTable(@"security_settings_crypto_sessions_description", @"Vector", nil) ];
}
}
else if (section == SECTION_ADVANCED)
{
switch (row)
{
case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES:
{
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
labelAndSwitchCell.mxkSwitch.enabled = YES;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell;
break;
}
case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION:
{
cell = [self descriptionCellForTableView:tableView
withText:NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices_description", @"Vector", nil) ];
break;
}
case ADVANCED_EXPORT:
{
MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!exportKeysBtnCell)
{
exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init];
}
else
{
exportKeysBtnCell.mxkButton.titleLabel.text = nil;
exportKeysBtnCell.mxkButton.enabled = YES;
}
NSString *btnTitle = NSLocalizedStringFromTable(@"security_settings_export_keys_manually", @"Vector", nil);
[exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
[exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
[exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
[exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
[exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside];
exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil;
cell = exportKeysBtnCell;
break;
}
}
}
else if (section == SECTION_KEYBACKUP)
{
cell = [keyBackupSection cellForRowAtRow:row];
}
else if (section == SECTION_DEBUG)
{
switch (row)
{
case DEBUG_CRYPTO_INFO:
{
MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath];
cryptoCell.mxkTextView.attributedText = [self cryptographyInformation];
cell = cryptoCell;
break;
}
case DEBUG_CROSSSIGNING_INFO:
{
MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath];
cryptoCell.mxkTextView.attributedText = [self crossSigningStatus];
cell = cryptoCell;
break;
}
case DEBUG_CROSSSIGNING_BOOTSTRAP:
{
MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!exportKeysBtnCell)
{
exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init];
}
NSString *btnTitle = @"Bootstrap cross-signing";
[exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
[exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
[exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
[exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
//[exportKeysBtnCell.mxkButton addTarget:self action:@selector(bootstrapCrossSigning:) forControlEvents:UIControlEventTouchUpInside];
exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil;
MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning;
exportKeysBtnCell.mxkButton.enabled = NO; //!crossSigning.myUserCrossSigningKeys;
cell = exportKeysBtnCell;
break;
}
}
}
return cell;
}
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
switch (section)
{
case SECTION_CRYPTO_SESSIONS:
return NSLocalizedStringFromTable(@"security_settings_crypto_sessions", @"Vector", nil);
case SECTION_KEYBACKUP:
return NSLocalizedStringFromTable(@"security_settings_backup", @"Vector", nil);
case SECTION_ADVANCED:
return NSLocalizedStringFromTable(@"security_settings_advanced", @"Vector", nil);
case SECTION_DEBUG:
return @"DEBUG";
}
return nil;
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
if ([view isKindOfClass:UITableViewHeaderFooterView.class])
{
// Customize label style
UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view;
tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
tableViewHeaderFooterView.textLabel.font = [UIFont systemFontOfSize:15];
}
}
#pragma mark - UITableView delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
cell.backgroundColor = ThemeService.shared.theme.backgroundColor;
if (cell.selectionStyle != UITableViewCellSelectionStyleNone)
{
// Update the selected background view
if (ThemeService.shared.theme.selectedBackgroundColor)
{
cell.selectedBackgroundView = [[UIView alloc] init];
cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor;
}
else
{
if (tableView.style == UITableViewStylePlain)
{
cell.selectedBackgroundView = nil;
}
else
{
cell.selectedBackgroundView.backgroundColor = nil;
}
}
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == SECTION_CRYPTO_SESSIONS)
{
return 44;
}
return 24;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 24;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.tableView == tableView)
{
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
if (section == SECTION_CRYPTO_SESSIONS)
{
NSUInteger deviceIndex = row;
if (deviceIndex < devicesArray.count)
{
ManageSessionViewController *viewController = [ManageSessionViewController instantiateWithMatrixSession:self.mainSession andDevice:devicesArray[deviceIndex]];
[self pushViewController:viewController];
}
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application
{
// If iOS wants to call this method, this is the right time to remove the file
[self deleteKeyExportFile];
}
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
{
documentInteractionController = nil;
}
#pragma mark - actions
- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer
{
[currentAlert dismissViewControllerAnimated:NO completion:nil];
exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession];
currentAlert = exportView.alertController;
// Use a temporary file for the export
keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"riot-keys.txt"]];
// Make sure the file is empty
[self deleteKeyExportFile];
// Show the export dialog
MXWeakify(self);
[exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
self->exportView = nil;
if (success)
{
// Let another app handling this file
self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:self->keyExportsFile];
[self->documentInteractionController setDelegate:self];
if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES])
{
// We want to delete the temp keys file after it has been processed by the other app.
// We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that
// but it is not reliable for all cases (see http://stackoverflow.com/a/21867096).
// So, arm a timer to auto delete the file after 10mins.
self->keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO];
}
else
{
self->documentInteractionController = nil;
[self deleteKeyExportFile];
}
}
}];
}
- (void)deleteKeyExportFile
{
// Cancel the deletion timer if it is still here
if (keyExportsFileDeletionTimer)
{
[keyExportsFileDeletionTimer invalidate];
keyExportsFileDeletionTimer = nil;
}
// And delete the file
if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path])
{
[[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil];
}
}
- (void)toggleBlacklistUnverifiedDevices:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
self.mainSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on;
[self.tableView reloadData];
}
#pragma mark - SettingsKeyBackupTableViewSectionDelegate
- (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
{
[self.tableView reloadData];
}
- (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow
{
return [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:textCellForRow inSection:SECTION_KEYBACKUP]];
}
- (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow
{
MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!cell)
{
cell = [[MXKTableViewCellWithButton alloc] init];
}
else
{
// Fix https://github.com/vector-im/riot-ios/issues/1354
cell.mxkButton.titleLabel.text = nil;
cell.mxkButton.enabled = YES;
}
cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
[cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
return cell;
}
- (void)settingsKeyBackupTableViewSectionShowKeyBackupSetup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
{
[self showKeyBackupSetupFromSignOutFlow:NO];
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion
{
[self showKeyBackupRecover:keyBackupVersion];
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion
{
MXWeakify(self);
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert =
[UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_title", @"Vector", nil)
message:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_msg", @"Vector", nil)
preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_button_delete", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
[self->keyBackupSection deleteWithKeyBackupVersion:keyBackupVersion];
}]];
[currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"];
[self presentViewController:currentAlert animated:YES completion:nil];
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showActivityIndicator:(BOOL)show
{
if (show)
{
[self startActivityIndicator];
}
else
{
[self stopActivityIndicator];
}
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showError:(NSError *)error
{
[[AppDelegate theDelegate] showErrorAsAlert:error];
}
#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter
- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow
{
keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
[keyBackupSetupCoordinatorBridgePresenter presentFrom:self
isStartedFromSignOut:showFromSignOutFlow
animated:true];
keyBackupSetupCoordinatorBridgePresenter.delegate = self;
}
- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupSetupCoordinatorBridgePresenter = nil;
}
- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupSetupCoordinatorBridgePresenter = nil;
[keyBackupSection reload];
}
#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter
- (void)showKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion
{
keyBackupRecoverCoordinatorBridgePresenter = [[KeyBackupRecoverCoordinatorBridgePresenter alloc] initWithSession:self.mainSession keyBackupVersion:keyBackupVersion];
[keyBackupRecoverCoordinatorBridgePresenter presentFrom:self animated:true];
keyBackupRecoverCoordinatorBridgePresenter.delegate = self;
}
- (void)keyBackupRecoverCoordinatorBridgePresenterDidCancel:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupRecoverCoordinatorBridgePresenter = nil;
}
- (void)keyBackupRecoverCoordinatorBridgePresenterDidRecover:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupRecoverCoordinatorBridgePresenter = nil;
}
@end

View file

@ -16,11 +16,9 @@
#import <MatrixKit/MatrixKit.h>
#import "DeviceView.h"
#import "MediaPickerViewController.h"
@interface SettingsViewController : MXKTableViewController<UITextFieldDelegate, MXKDeviceViewDelegate, UIDocumentInteractionControllerDelegate, MXKCountryPickerViewControllerDelegate, MXKLanguagePickerViewControllerDelegate, MXKDataSourceDelegate>
@interface SettingsViewController : MXKTableViewController<UITextFieldDelegate, MXKCountryPickerViewControllerDelegate, MXKLanguagePickerViewControllerDelegate, MXKDataSourceDelegate>
@end

View file

@ -32,6 +32,7 @@
#import "CountryPickerViewController.h"
#import "LanguagePickerViewController.h"
#import "DeactivateAccountViewController.h"
#import "SecurityViewController.h"
#import "NBPhoneNumberUtil.h"
#import "RageShakeManager.h"
@ -51,6 +52,7 @@ enum
{
SETTINGS_SECTION_SIGN_OUT_INDEX = 0,
SETTINGS_SECTION_USER_SETTINGS_INDEX,
SETTINGS_SECTION_SECURITY_INDEX,
SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX,
SETTINGS_SECTION_CALLS_INDEX,
SETTINGS_SECTION_DISCOVERY_INDEX,
@ -62,9 +64,6 @@ enum
SETTINGS_SECTION_ADVANCED_INDEX,
SETTINGS_SECTION_OTHER_INDEX,
SETTINGS_SECTION_LABS_INDEX,
SETTINGS_SECTION_CRYPTOGRAPHY_INDEX,
SETTINGS_SECTION_KEYBACKUP_INDEX,
SETTINGS_SECTION_DEVICES_INDEX,
SETTINGS_SECTION_FLAIR_INDEX,
SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX,
SETTINGS_SECTION_COUNT
@ -136,23 +135,14 @@ enum
{
LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0,
LABS_USE_JITSI_WIDGET_INDEX,
LABS_CRYPTO_INDEX,
LABS_COUNT, // TODO: Remove it once features exist
LABS_DM_KEY_VERIFICATION_INDEX,
LABS_CROSS_SIGNING_INDEX,
// LABS_COUNT
};
enum {
CRYPTOGRAPHY_INFO_INDEX = 0,
CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX,
CRYPTOGRAPHY_EXPORT_INDEX,
CRYPTOGRAPHY_COUNT
LABS_COUNT
};
enum
{
DEVICES_DESCRIPTION_INDEX = 0
SECURITY_BUTTON_INDEX = 0,
SECURITY_COUNT
};
#define SECTION_TITLE_PADDING_WHEN_HIDDEN 0.01f
@ -161,10 +151,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(void);
@interface SettingsViewController () <DeactivateAccountViewControllerDelegate,
SettingsKeyBackupTableViewSectionDelegate,
MXKEncryptionInfoViewDelegate,
KeyBackupSetupCoordinatorBridgePresenterDelegate,
KeyBackupRecoverCoordinatorBridgePresenterDelegate,
SignOutAlertPresenterDelegate,
SingleImagePickerPresenterDelegate,
SettingsDiscoveryTableViewSectionDelegate, SettingsDiscoveryViewModelCoordinatorDelegate,
@ -223,10 +210,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
NSInteger localContactsSyncIndex;
NSInteger localContactsPhoneBookCountryIndex;
// Devices
NSMutableArray<MXDevice *> *devicesArray;
DeviceView *deviceView;
// Flair: the groups data source
GroupsDataSource *groupsDataSource;
@ -245,23 +228,13 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
//
UIAlertController *resetPwdAlertController;
// The view used to export e2e keys
MXKEncryptionKeysExportView *exportView;
// The document interaction Controller used to export e2e keys
UIDocumentInteractionController *documentInteractionController;
NSURL *keyExportsFile;
NSTimer *keyExportsFileDeletionTimer;
BOOL keepNewEmailEditing;
BOOL keepNewPhoneNumberEditing;
// The current pushed view controller
UIViewController *pushedViewController;
SettingsKeyBackupTableViewSection *keyBackupSection;
KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter;
KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter;
SettingsIdentityServerCoordinatorBridgePresenter *identityServerSettingsCoordinatorBridgePresenter;
}
@ -361,18 +334,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
[self addMatrixSession:mxSession];
}
if (self.mainSession.crypto.backup)
{
MXDeviceInfo *deviceInfo = [self.mainSession.crypto.deviceList storedDevice:self.mainSession.matrixRestClient.credentials.userId
deviceId:self.mainSession.matrixRestClient.credentials.deviceId];
if (deviceInfo)
{
keyBackupSection = [[SettingsKeyBackupTableViewSection alloc] initWithKeyBackup:self.mainSession.crypto.backup userDevice:deviceInfo];
keyBackupSection.delegate = self;
}
}
[self setupDiscoverySection];
groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:self.mainSession];
@ -435,13 +396,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
// Release the potential pushed view controller
[self releasePushedViewController];
if (documentInteractionController)
{
[documentInteractionController dismissPreviewAnimated:NO];
[documentInteractionController dismissMenuAnimated:NO];
documentInteractionController = nil;
}
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
@ -470,7 +424,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
}
keyBackupSetupCoordinatorBridgePresenter = nil;
keyBackupRecoverCoordinatorBridgePresenter = nil;
identityServerSettingsCoordinatorBridgePresenter = nil;
}
@ -506,12 +459,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
// Refresh linked emails and phone numbers in parallel
[self loadAccount3PIDs];
// Refresh the current device information in parallel
[self loadCurrentDeviceInformation];
// Refresh devices in parallel
[self loadDevices];
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -640,12 +587,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
[[NSNotificationCenter defaultCenter] removeObserver:self];
onReadyToDestroyHandler = nil;
if (deviceView)
{
[deviceView removeFromSuperview];
deviceView = nil;
}
}
-(void)setNewEmailEditingEnabled:(BOOL)newEmailEditingEnabled
@ -994,171 +935,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
}];
}
- (void)loadCurrentDeviceInformation
{
// Refresh the current device information
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
[account loadDeviceInformation:^{
// Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self refreshSettings];
} failure:nil];
}
- (NSAttributedString*)cryptographyInformation
{
// TODO Handle multi accounts
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
// Crypto information
NSMutableAttributedString *cryptoInformationString = [[NSMutableAttributedString alloc]
initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_name", @"Vector", nil)
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:account.device.displayName ? account.device.displayName : @""
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_id", @"Vector", nil)
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:account.device.deviceId ? account.device.deviceId : @""
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_key", @"Vector", nil)
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
NSString *fingerprint = account.mxSession.crypto.deviceEd25519Key;
if (fingerprint)
{
fingerprint = [MXTools addWhiteSpacesToString:fingerprint every:4];
}
[cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:fingerprint ? fingerprint : @""
attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]];
return cryptoInformationString;
}
- (void)loadDevices
{
// Refresh the account devices list
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
[account.mxRestClient devices:^(NSArray<MXDevice *> *devices) {
if (devices)
{
devicesArray = [NSMutableArray arrayWithArray:devices];
// Sort devices according to the last seen date.
NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) {
if (deviceA.lastSeenTs > deviceB.lastSeenTs)
{
return NSOrderedAscending;
}
if (deviceA.lastSeenTs < deviceB.lastSeenTs)
{
return NSOrderedDescending;
}
return NSOrderedSame;
};
// Sort devices list
[devicesArray sortUsingComparator:comparator];
}
else
{
devicesArray = nil;
}
// Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self refreshSettings];
} failure:^(NSError *error) {
// Display the data that has been loaded last time
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self refreshSettings];
}];
}
- (void)showDeviceDetails:(MXDevice *)device
{
[self dismissKeyboard];
deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession];
deviceView.delegate = self;
// Add the view and define edge constraints
[self.tableView.superview addSubview:deviceView];
[self.tableView.superview bringSubviewToFront:deviceView];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.0f];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeLeft
multiplier:1.0f
constant:0.0f];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeHeight
multiplier:1.0f
constant:0.0f];
[NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]];
}
- (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert
{
[self dismissKeyboard];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdated
{
[deviceView removeFromSuperview];
deviceView = nil;
if (isUpdated)
{
[self loadDevices];
}
}
- (void)editNewEmailTextField
{
if (newEmailTextField && ![newEmailTextField becomeFirstResponder])
@ -1457,35 +1233,15 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
}
}
}
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
{
count = devicesArray.count;
if (count)
{
// For some description (DEVICES_DESCRIPTION_INDEX)
count++;
}
}
else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX)
{
// Check whether this section is visible.
if (self.mainSession.crypto)
{
count = CRYPTOGRAPHY_COUNT;
}
}
else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX)
{
// Check whether this section is visible.
if (self.mainSession.crypto)
{
count = keyBackupSection.numberOfRows;
}
}
else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX)
{
count = 1;
}
else if (section == SETTINGS_SECTION_SECURITY_INDEX)
{
count = SECURITY_COUNT;
}
return count;
}
@ -2430,45 +2186,14 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
cell = labelAndSwitchCell;
}
else if (row == LABS_CRYPTO_INDEX)
{
MXSession* session = [AppDelegate theDelegate].mxSessions[0];
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_e2e_encryption", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = (nil != session.crypto);
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsEndToEndEncryption:) forControlEvents:UIControlEventTouchUpInside];
if (session.crypto)
{
// Once crypto is enabled, it is enabled
labelAndSwitchCell.mxkSwitch.enabled = NO;
}
cell = labelAndSwitchCell;
}
else if (row == LABS_DM_KEY_VERIFICATION_INDEX)
{
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_dm_key_verification", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableDMKeyVerification;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsDMKeyVerification:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell;
}
else if (row == LABS_CROSS_SIGNING_INDEX)
{
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_cross_signing", @"Vector", nil);
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_enable_cross_signing", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableCrossSigning;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
labelAndSwitchCell.mxkSwitch.enabled = YES;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsCrossSigning:) forControlEvents:UIControlEventTouchUpInside];
@ -2501,93 +2226,16 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
[groupWithSwitchCell.toggleButton addTarget:self action:@selector(toggleCommunityFlair:) forControlEvents:UIControlEventTouchUpInside];
}
}
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
else if (section == SETTINGS_SECTION_SECURITY_INDEX)
{
if (row == DEVICES_DESCRIPTION_INDEX)
switch (row)
{
MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView];
descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_devices_description", @"Vector", nil);
descriptionCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
descriptionCell.textLabel.font = [UIFont systemFontOfSize:15];
descriptionCell.textLabel.numberOfLines = 0;
descriptionCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone;
cell = descriptionCell;
case SECURITY_BUTTON_INDEX:
cell = [self getDefaultTableViewCell:tableView];
cell.textLabel.text = NSLocalizedStringFromTable(@"security_settings_title", @"Vector", nil);
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
break;
}
else
{
NSUInteger deviceIndex = row - 1;
MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView];
if (deviceIndex < devicesArray.count)
{
NSString *name = devicesArray[deviceIndex].displayName;
NSString *deviceId = devicesArray[deviceIndex].deviceId;
deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]);
deviceCell.textLabel.numberOfLines = 0;
if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId])
{
deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17];
}
}
cell = deviceCell;
}
}
else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX)
{
if (row == CRYPTOGRAPHY_INFO_INDEX)
{
MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath];
cryptoCell.mxkTextView.attributedText = [self cryptographyInformation];
cell = cryptoCell;
}
else if (row == CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX)
{
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil);
labelAndSwitchCell.mxkSwitch.on = account.mxSession.crypto.globalBlacklistUnverifiedDevices;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
labelAndSwitchCell.mxkSwitch.enabled = YES;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell;
}
else if (row == CRYPTOGRAPHY_EXPORT_INDEX)
{
MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!exportKeysBtnCell)
{
exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init];
}
else
{
// Fix https://github.com/vector-im/riot-ios/issues/1354
exportKeysBtnCell.mxkButton.titleLabel.text = nil;
}
NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil);
[exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
[exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
[exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
[exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
[exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside];
exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil;
cell = exportKeysBtnCell;
}
}
else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX)
{
cell = [keyBackupSection cellForRowAtRow:row];
}
else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX)
{
@ -2685,29 +2333,9 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
return NSLocalizedStringFromTable(@"settings_flair", @"Vector", nil);
}
}
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
else if (section == SETTINGS_SECTION_SECURITY_INDEX)
{
// Check whether this section is visible
if (devicesArray.count > 0)
{
return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil);
}
}
else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX)
{
// Check whether this section is visible
if (self.mainSession.crypto)
{
return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil);
}
}
else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX)
{
// Check whether this section is visible
if (self.mainSession.crypto)
{
return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil);
}
return NSLocalizedStringFromTable(@"settings_security", @"Vector", nil);
}
else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX)
{
@ -3033,17 +2661,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
}
}
}
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
{
if (row > DEVICES_DESCRIPTION_INDEX)
{
NSUInteger deviceIndex = row - 1;
if (deviceIndex < devicesArray.count)
{
[self showDeviceDetails:devicesArray[deviceIndex]];
}
}
}
else if (section == SETTINGS_SECTION_CONTACTS_INDEX)
{
if (row == localContactsPhoneBookCountryIndex)
@ -3055,6 +2672,19 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
[self pushViewController:countryPicker];
}
}
else if (section == SETTINGS_SECTION_SECURITY_INDEX)
{
switch (row)
{
case SECURITY_BUTTON_INDEX:
{
SecurityViewController *securityViewController = [SecurityViewController instantiateWithMatrixSession:self.mainSession];
[self pushViewController:securityViewController];
break;
}
}
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@ -3421,132 +3051,13 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
}
}
- (void)toggleLabsEndToEndEncryption:(id)sender
{
if (sender && [sender isKindOfClass:UISwitch.class])
{
UISwitch *switchButton = (UISwitch*)sender;
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
if (switchButton.isOn && !account.mxCredentials.deviceId.length)
{
// Prompt the user to log in again when no device id is available.
__weak typeof(self) weakSelf = self;
// Prompt user
NSString *msg = NSLocalizedStringFromTable(@"settings_labs_e2e_encryption_prompt_message", @"Vector", nil);
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = [UIAlertController alertControllerWithTitle:nil message:msg preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"later"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
// Reset toggle button
[switchButton setOn:NO animated:YES];
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
switchButton.enabled = NO;
[self startActivityIndicator];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[[AppDelegate theDelegate] logoutWithConfirmation:NO completion:nil];
});
}
}]];
[currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCEnableEncryptionAlert"];
[self presentViewController:currentAlert animated:YES completion:nil];
}
else
{
[self startActivityIndicator];
MXSession* session = [AppDelegate theDelegate].mxSessions[0];
[session enableCrypto:switchButton.isOn success:^{
// When disabling crypto, reset the current device id as it cannot be reused.
// This means that the user will need to log in again if he wants to re-enable e2e.
if (!switchButton.isOn)
{
[account resetDeviceId];
}
// Reload all data source of encrypted rooms
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:session];
for (MXRoom *room in session.rooms)
{
if (room.summary.isEncrypted)
{
[roomDataSourceManager roomDataSourceForRoom:room.roomId create:NO onComplete:^(MXKRoomDataSource *roomDataSource) {
[roomDataSource reload];
}];
}
}
// Once crypto is enabled, it is enabled
switchButton.enabled = NO;
[self stopActivityIndicator];
// Refresh table view to add cryptography information.
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopActivityIndicator];
// Come back to previous state button
[switchButton setOn:!switchButton.isOn animated:YES];
}];
}
}
}
- (void)toggleLabsDMKeyVerification:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
RiotSettings.shared.enableDMKeyVerification = switchButton.isOn;
}
- (void)toggleLabsCrossSigning:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
RiotSettings.shared.enableCrossSigning = switchButton.isOn;
}
- (void)toggleBlacklistUnverifiedDevices:(id)sender
{
UISwitch *switchButton = (UISwitch*)sender;
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
account.mxSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on;
[self.tableView reloadData];
self.mainSession.crypto.warnOnUnknowDevices = !RiotSettings.shared.enableCrossSigning;
}
- (void)togglePinRoomsWithMissedNotif:(id)sender
@ -4145,69 +3656,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
self.imagePickerPresenter = singleImagePickerPresenter;
}
- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer
{
[currentAlert dismissViewControllerAnimated:NO completion:nil];
exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession];
currentAlert = exportView.alertController;
// Use a temporary file for the export
keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"riot-keys.txt"]];
// Make sure the file is empty
[self deleteKeyExportFile];
// Show the export dialog
__weak typeof(self) weakSelf = self;
[exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
self->exportView = nil;
if (success)
{
// Let another app handling this file
self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:keyExportsFile];
[self->documentInteractionController setDelegate:self];
if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES])
{
// We want to delete the temp keys file after it has been processed by the other app.
// We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that
// but it is not reliable for all cases (see http://stackoverflow.com/a/21867096).
// So, arm a timer to auto delete the file after 10mins.
keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO];
}
else
{
self->documentInteractionController = nil;
[self deleteKeyExportFile];
}
}
}
}];
}
- (void)deleteKeyExportFile
{
// Cancel the deletion timer if it is still here
if (keyExportsFileDeletionTimer)
{
[keyExportsFileDeletionTimer invalidate];
keyExportsFileDeletionTimer = nil;
}
// And delete the file
if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path])
{
[[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil];
}
}
- (void)showThemePicker
{
__weak typeof(self) weakSelf = self;
@ -4569,18 +4017,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
[self presentViewController:resetPwdAlertController animated:YES completion:nil];
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application
{
// If iOS wants to call this method, this is the right time to remove the file
[self deleteKeyExportFile];
}
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
{
documentInteractionController = nil;
}
#pragma mark - MXKCountryPickerViewControllerDelegate
@ -4664,96 +4100,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
[deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - SettingsKeyBackupTableViewSectionDelegate
- (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
{
[self.tableView reloadData];
}
- (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow
{
return [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:textCellForRow inSection:SETTINGS_SECTION_KEYBACKUP_INDEX]];
}
- (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow
{
MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!cell)
{
cell = [[MXKTableViewCellWithButton alloc] init];
}
else
{
// Fix https://github.com/vector-im/riot-ios/issues/1354
cell.mxkButton.titleLabel.text = nil;
}
cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
[cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
return cell;
}
- (void)settingsKeyBackupTableViewSectionShowKeyBackupSetup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
{
[self showKeyBackupSetupFromSignOutFlow:NO];
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion
{
[self showKeyBackupRecover:keyBackupVersion];
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion
{
MXWeakify(self);
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert =
[UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_title", @"Vector", nil)
message:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_msg", @"Vector", nil)
preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_button_delete", @"Vector", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
[self->keyBackupSection deleteWithKeyBackupVersion:keyBackupVersion];
}]];
[currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"];
[self presentViewController:currentAlert animated:YES completion:nil];
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showActivityIndicator:(BOOL)show
{
if (show)
{
[self startActivityIndicator];
}
else
{
[self stopActivityIndicator];
}
}
- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showError:(NSError *)error
{
[[AppDelegate theDelegate] showErrorAsAlert:error];
}
#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter
#pragma mark - KeyBackupSetupCoordinatorBridgePresenter
- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow
{
@ -4774,28 +4122,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate>
- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupSetupCoordinatorBridgePresenter = nil;
[keyBackupSection reload];
}
#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter
- (void)showKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion
{
keyBackupRecoverCoordinatorBridgePresenter = [[KeyBackupRecoverCoordinatorBridgePresenter alloc] initWithSession:self.mainSession keyBackupVersion:keyBackupVersion];
[keyBackupRecoverCoordinatorBridgePresenter presentFrom:self animated:true];
keyBackupRecoverCoordinatorBridgePresenter.delegate = self;
}
- (void)keyBackupRecoverCoordinatorBridgePresenterDidCancel:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupRecoverCoordinatorBridgePresenter = nil;
}
- (void)keyBackupRecoverCoordinatorBridgePresenterDidRecover:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter {
[keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true];
keyBackupRecoverCoordinatorBridgePresenter = nil;
}
#pragma mark - SignOutAlertPresenterDelegate

Some files were not shown because too many files have changed in this diff Show more