Merge pull request #2960 from vector-im/riot_2924

Cross-signing: New room member screen design (WIP)
This commit is contained in:
SBiOSoftWhare 2020-01-31 16:54:09 +01:00 committed by GitHub
commit c2d1442f94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 3271 additions and 108 deletions

View file

@ -179,6 +179,15 @@
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 */; };
@ -532,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 */; };
@ -878,6 +908,15 @@
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>"; };
@ -1416,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>"; };
@ -1690,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 */,
@ -2124,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 = (
@ -2513,6 +2590,7 @@
B1B5567620EE6C4C00210D55 /* Modules */ = {
isa = PBXGroup;
children = (
B1BEE71023DF28CA0003A4CB /* UserVerification */,
B1A12C64239AB74500AA2B86 /* CrossSigning */,
B1A6C10523881ECB002882FD /* SlidingModal */,
32DB556722FDADE50016329E /* ServiceTerms */,
@ -2712,6 +2790,7 @@
B1B556A620EE6C4C00210D55 /* Detail */ = {
isa = PBXGroup;
children = (
B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */,
B1B556A720EE6C4C00210D55 /* RoomMemberDetailsViewController.h */,
B1B556A820EE6C4C00210D55 /* RoomMemberDetailsViewController.m */,
B1B556A920EE6C4C00210D55 /* RoomMemberDetailsViewController.xib */,
@ -3668,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 = (
@ -4165,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 */,
@ -4187,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 */,
@ -4256,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 */,
@ -4549,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 */,
@ -4556,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 */,
@ -4564,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 */,
@ -4577,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 */,
@ -4605,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 */,
@ -4630,6 +4763,7 @@
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 */,
@ -4702,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 */,
@ -4713,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 */,
@ -4722,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 */,
@ -4733,6 +4873,7 @@
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 */,
@ -4744,6 +4885,7 @@
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 */,
@ -4766,6 +4908,7 @@
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 */,
@ -4780,6 +4923,7 @@
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 */,
@ -4794,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 */,
@ -4808,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 */,
@ -4824,6 +4971,7 @@
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 */,
@ -4837,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 */,
@ -4873,6 +5022,7 @@
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 */,
@ -4911,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 */,
@ -4920,10 +5071,12 @@
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 */,
@ -4932,6 +5085,7 @@
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

@ -140,6 +140,8 @@ extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey;
- (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest
inSession:(MXSession*)session;
- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession;
#pragma mark - Matrix Accounts handling
- (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection;

View file

@ -4890,6 +4890,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

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

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

@ -28,10 +28,13 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
private let navigationRouter: NavigationRouterType
private let session: MXSession
private let otherUserId: String
private let otherDeviceId: String
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 +57,13 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
self.otherUserId = otherUserId
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
///
@ -77,13 +87,29 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
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: Coordinator & Presentable
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()
}
@ -91,8 +117,15 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
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)
}
}
}
@ -117,6 +150,14 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType {
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)
@ -141,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()
@ -152,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)
}
@ -226,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

@ -63,6 +63,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) {

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

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

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 = "Verify user"
bodyTitle = VectorL10n.deviceVerificationVerifiedTitle
descriptionTextPart1 = "Youve successfully verified this user."
descriptionTextPart2 = "Messages with this user in this room are end-to-end encrypted and cant be read by third parties."
}
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
}
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 = "Verify user"
instructionText = isVerificationByEmoji ? "Verify this user by confirming the following unique emoji appears on their screen, in the same order." : "Verify this user by confirming the following numbers appear on their screen, in the same order."
adviceText = VectorL10n.deviceVerificationSecurityAdvice
}
self.informationLabel.text = VectorL10n.deviceVerificationSecurityAdvice
self.title = title
self.titleLabel.text = instructionText
self.informationLabel.text = adviceText
self.waitingPartnerLabel.text = VectorL10n.deviceVerificationVerifyWaitPartner
self.waitingPartnerLabel.isHidden = true
@ -223,10 +239,8 @@ extension DeviceVerificationVerifyViewController: UICollectionViewDataSource {
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VerifyEmojiCollectionViewCell", for: indexPath) as? VerifyEmojiCollectionViewCell else {
return UICollectionViewCell()
}
let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: VerifyEmojiCollectionViewCell.self)
guard let emoji = self.viewModel.emojis?[indexPath.row] else {
return UICollectionViewCell()

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

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

@ -33,10 +33,13 @@
#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;
NSMutableArray<NSNumber*> *securityActionsArray;
/**
List of the admin actions on this member.
*/
@ -78,6 +81,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 +128,7 @@
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
self.encryptionTrustLevel = UserEncryptionTrustLevelUnknown;
adminActionsArray = [[NSMutableArray alloc] init];
otherActionsArray = [[NSMutableArray alloc] init];
@ -195,10 +219,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 +291,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 +413,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 +438,130 @@
}
// 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, 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];
}
} 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];
}
}];
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];
}
} failure:^(NSError *error) {
NSLog(@"[RoomMemberDetailsVC] Crypto failed to download device info for user: %@", userId);
if (weakSelf)
{
// Restore the status bar
typeof(self) self = weakSelf;
// Notify the end user
NSString *myUserId = self.mainSession.myUser.userId;
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
}
}];
}
}
// Complete data update and reload table view
[super updateMemberInfo];
}
- (void)refreshUserEncryptionTrustLevel
{
NSString *userId = self.mxRoomMember.userId;
if (!userId)
{
return;
}
if (self.mxRoom.summary.isEncrypted && self.mxRoom.mxSession.crypto)
{
MXUsersTrustLevelSummary *usersTrustLevelSummary = [self.mxRoom.mxSession.crypto trustLevelSummaryForUserIds:@[userId]];
double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted;
UserEncryptionTrustLevel userEncryptionTrustLevel;
if (trustedDevicesPercentage >= 1.0)
{
userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted;
}
else if (trustedDevicesPercentage == 0.0)
{
userEncryptionTrustLevel = UserEncryptionTrustLevelNormal;
}
else
{
userEncryptionTrustLevel = UserEncryptionTrustLevelWarning;
}
self.encryptionTrustLevel = userEncryptionTrustLevel;
[self updateMemberInfo];
}
else
{
self.encryptionTrustLevel = UserEncryptionTrustLevelNone;
[self updateMemberInfo];
}
}
- (UIImage*)userEncryptionBadgeImage
{
NSString *encryptionIconName;
UIImage *encryptionIcon;
UserEncryptionTrustLevel userEncryptionTrustLevel = self.encryptionTrustLevel;
switch (userEncryptionTrustLevel) {
case RoomEncryptionTrustLevelWarning:
encryptionIconName = @"encryption_warning";
break;
case RoomEncryptionTrustLevelNormal:
encryptionIconName = @"encryption_normal";
break;
case RoomEncryptionTrustLevelTrusted:
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 +610,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 +744,12 @@
}
}
adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1;
securityIndex = adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1;
if (RiotSettings.shared.enableCrossSigning)
{
securityIndex = sectionCount++;
}
if (otherActionsArray.count)
{
@ -644,7 +775,23 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == adminToolsIndex)
if (section == securityIndex)
{
NSInteger numberOfRows;
switch (self.encryptionTrustLevel) {
case UserEncryptionTrustLevelUnknown:
case UserEncryptionTrustLevelNone:
numberOfRows = 1;
break;
default:
numberOfRows = 2;
break;
}
return numberOfRows;
}
else if (section == adminToolsIndex)
{
return adminActionsArray.count;
}
@ -666,10 +813,18 @@
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (section == adminToolsIndex)
if (section == securityIndex)
{
return @"SECURITY";
}
else if (section == adminToolsIndex)
{
return NSLocalizedStringFromTable(@"room_participants_action_section_admin_tools", @"Vector", nil);
}
else if (RiotSettings.shared.enableCrossSigning && section == otherActionsIndex)
{
return @"OPTIONS";
}
else if (section == directChatsIndex)
{
return NSLocalizedStringFromTable(@"room_participants_action_section_direct_chats", @"Vector", nil);
@ -741,7 +896,78 @@
{
UITableViewCell *cell;
if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex)
if (indexPath.section == securityIndex)
{
if (indexPath.row == [self tableView:self.tableView numberOfRowsInSection:indexPath.section] - 1)
{
MXKTableViewCell *encryptionInfoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath];
NSMutableString *encryptionInformation = [NSMutableString new];
switch (self.encryptionTrustLevel) {
case UserEncryptionTrustLevelUnknown:
[encryptionInformation appendString:@"Loading"];
break;
case UserEncryptionTrustLevelNone:
[encryptionInformation appendString:@"Messages in this room are not end-to-end encrypted."];
break;
default:
[encryptionInformation appendString:@"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."];
break;
}
[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
{
MXKTableViewCell *securityStatusCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath];
NSString *statusText;
switch (self.encryptionTrustLevel) {
case UserEncryptionTrustLevelTrusted:
statusText = @"Verified";
break;
case UserEncryptionTrustLevelNormal:
statusText = @"Verify";
break;
case UserEncryptionTrustLevelWarning:
statusText = @"Warning";
break;
default:
statusText = @"Loading";
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 (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex)
{
TableViewCellWithButton *cellWithButton = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath];
@ -852,26 +1078,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 +1090,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

@ -0,0 +1,75 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class UserVerificationSessionStatusCoordinator: UserVerificationSessionStatusCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let userId: String
private let deviceId: String
private var userVerificationSessionStatusViewModel: UserVerificationSessionStatusViewModelType
private let userVerificationSessionStatusViewController: UserVerificationSessionStatusViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: UserVerificationSessionStatusCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, userId: String, userDisplayName: String?, deviceId: String) {
self.session = session
self.userId = userId
self.deviceId = deviceId
let userVerificationSessionStatusViewModel = UserVerificationSessionStatusViewModel(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId)
let userVerificationSessionStatusViewController = UserVerificationSessionStatusViewController.instantiate(with: userVerificationSessionStatusViewModel)
self.userVerificationSessionStatusViewModel = userVerificationSessionStatusViewModel
self.userVerificationSessionStatusViewController = userVerificationSessionStatusViewController
}
// MARK: - Public methods
func start() {
self.userVerificationSessionStatusViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.userVerificationSessionStatusViewController
}
}
// MARK: - UserVerificationSessionStatusViewModelCoordinatorDelegate
extension UserVerificationSessionStatusCoordinator: UserVerificationSessionStatusViewModelCoordinatorDelegate {
func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) {
self.delegate?.userVerificationSessionStatusCoordinator(self, wantsToManuallyVerifyDeviceWithId: deviceId, for: userId)
}
func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType) {
self.delegate?.userVerificationSessionStatusCoordinatorDidClose(self)
}
}

View file

@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationSessionStatusCoordinatorDelegate: class {
func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String)
func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType)
}
/// `UserVerificationSessionStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol UserVerificationSessionStatusCoordinatorType: Coordinator, Presentable {
var delegate: UserVerificationSessionStatusCoordinatorDelegate? { get }
}

View file

@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// UserVerificationSessionStatusViewController view actions exposed to view model
enum UserVerificationSessionStatusViewAction {
case loadData
case verify
case close
}

View file

@ -0,0 +1,209 @@
<?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="edG-Ef-G5W">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--User Verification Session Status View Controller-->
<scene sceneID="yMy-34-3pS">
<objects>
<viewController id="edG-Ef-G5W" customClass="UserVerificationSessionStatusViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="G1A-RF-WrC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="16P-bA-3wP">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sX2-Xu-9f5">
<rect key="frame" x="0.0" y="0.0" width="375" height="296.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1lx-d3-6c2">
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qdf-kG-wgx">
<rect key="frame" x="325" y="20" width="30" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="Pon-L2-rt1"/>
<constraint firstAttribute="width" constant="30" id="qav-Na-EDh"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<state key="normal" image="close_button"/>
<connections>
<action selector="closeButtonAction:" destination="edG-Ef-G5W" eventType="touchUpInside" id="0qe-Df-Nw1"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="0d0-9z-5Ar">
<rect key="frame" x="20" y="23" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="0d0-9z-5Ar" secondAttribute="height" multiplier="1:1" id="EYK-FL-Zx6"/>
<constraint firstAttribute="height" constant="24" id="MuJ-Ex-1Vk"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Warning" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mXI-Sg-wYe">
<rect key="frame" x="54" y="23" width="261" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Messages with this user in this room are end-to-end encrypted and cant be read by third parties." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CTY-XM-iLf">
<rect key="frame" x="20" y="62" width="335" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="Qdf-kG-wgx" secondAttribute="trailing" constant="20" id="2JS-ui-NNI"/>
<constraint firstItem="Qdf-kG-wgx" firstAttribute="leading" secondItem="mXI-Sg-wYe" secondAttribute="trailing" constant="10" id="7VI-oy-V0w"/>
<constraint firstItem="CTY-XM-iLf" firstAttribute="top" secondItem="mXI-Sg-wYe" secondAttribute="bottom" constant="15" id="8UG-sf-OQU"/>
<constraint firstItem="mXI-Sg-wYe" firstAttribute="centerY" secondItem="Qdf-kG-wgx" secondAttribute="centerY" id="Ad5-Kd-ox8"/>
<constraint firstItem="0d0-9z-5Ar" firstAttribute="centerY" secondItem="mXI-Sg-wYe" secondAttribute="centerY" id="Jlp-rC-N2E"/>
<constraint firstItem="CTY-XM-iLf" firstAttribute="trailing" secondItem="Qdf-kG-wgx" secondAttribute="trailing" id="LK6-M8-kCT"/>
<constraint firstAttribute="bottom" secondItem="CTY-XM-iLf" secondAttribute="bottom" constant="10" id="MrU-Ar-zkT"/>
<constraint firstItem="mXI-Sg-wYe" firstAttribute="leading" secondItem="0d0-9z-5Ar" secondAttribute="trailing" constant="10" id="O4U-Wa-zdT"/>
<constraint firstItem="CTY-XM-iLf" firstAttribute="leading" secondItem="0d0-9z-5Ar" secondAttribute="leading" id="UeO-oR-wzQ"/>
<constraint firstItem="Qdf-kG-wgx" firstAttribute="top" secondItem="1lx-d3-6c2" secondAttribute="top" constant="20" id="WOL-w8-7T3"/>
<constraint firstItem="0d0-9z-5Ar" firstAttribute="leading" secondItem="1lx-d3-6c2" secondAttribute="leading" constant="20" id="cMk-cs-f9i"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3Mu-7j-flw">
<rect key="frame" x="0.0" y="108" width="375" height="40.5"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="0ME-4K-MWA" customClass="encryption_warning">
<rect key="frame" x="20" y="13.5" width="14" height="14"/>
<constraints>
<constraint firstAttribute="width" secondItem="0ME-4K-MWA" secondAttribute="height" multiplier="1:1" id="r0c-Ix-lm4"/>
<constraint firstAttribute="width" constant="14" id="zKC-ZT-6y8"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Riot iOS (BLPOWKUPGQ)" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FET-ZT-Q69">
<rect key="frame" x="44" y="10" width="311" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="0ME-4K-MWA" firstAttribute="leading" secondItem="3Mu-7j-flw" secondAttribute="leading" constant="20" id="8W6-Zu-Qu2"/>
<constraint firstAttribute="trailing" secondItem="FET-ZT-Q69" secondAttribute="trailing" constant="20" id="BbB-ge-jNu"/>
<constraint firstItem="FET-ZT-Q69" firstAttribute="top" secondItem="3Mu-7j-flw" secondAttribute="top" constant="10" id="Bw3-IL-AwO"/>
<constraint firstAttribute="bottom" secondItem="FET-ZT-Q69" secondAttribute="bottom" constant="10" id="NGo-OD-AHt"/>
<constraint firstItem="FET-ZT-Q69" firstAttribute="leading" secondItem="0ME-4K-MWA" secondAttribute="trailing" constant="10" id="Sap-sa-3tW"/>
<constraint firstItem="0ME-4K-MWA" firstAttribute="centerY" secondItem="FET-ZT-Q69" secondAttribute="centerY" id="W4V-Dz-CKf"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="rB7-Ct-Mq7">
<rect key="frame" x="0.0" y="148.5" width="375" height="148"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oRN-W6-TVF">
<rect key="frame" x="0.0" y="0.0" width="375" height="148"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dd2-N5-GEx">
<rect key="frame" x="20" y="10" width="335" height="54"/>
<string key="text">Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HiI-uV-Hjw">
<rect key="frame" x="20" y="84" width="335" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCLoginButton"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="LKd-e3-0AI"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="exF-8e-73K"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Manually verify">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="verifyButtonAction:" destination="edG-Ef-G5W" eventType="touchUpInside" id="gGx-OT-X5w"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="HiI-uV-Hjw" secondAttribute="bottom" constant="20" id="3bP-Hs-m42"/>
<constraint firstAttribute="trailing" secondItem="dd2-N5-GEx" secondAttribute="trailing" constant="20" id="BHw-Yv-ceG"/>
<constraint firstItem="dd2-N5-GEx" firstAttribute="leading" secondItem="oRN-W6-TVF" secondAttribute="leading" constant="20" id="BV7-Vu-PQR"/>
<constraint firstItem="HiI-uV-Hjw" firstAttribute="top" secondItem="dd2-N5-GEx" secondAttribute="bottom" constant="20" id="BtT-Mg-Okq"/>
<constraint firstItem="HiI-uV-Hjw" firstAttribute="centerX" secondItem="oRN-W6-TVF" secondAttribute="centerX" id="Ign-Yp-tpB"/>
<constraint firstItem="dd2-N5-GEx" firstAttribute="top" secondItem="oRN-W6-TVF" secondAttribute="top" constant="10" id="Ygn-tl-ahK"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="HiI-uV-Hjw" secondAttribute="trailing" constant="20" id="nqA-lS-ubx"/>
<constraint firstItem="HiI-uV-Hjw" firstAttribute="width" secondItem="oRN-W6-TVF" secondAttribute="width" priority="250" id="qp1-z5-eP9"/>
<constraint firstItem="HiI-uV-Hjw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="oRN-W6-TVF" secondAttribute="leading" constant="20" id="uNl-Ok-uee"/>
<constraint firstAttribute="height" priority="250" id="yuI-ch-y92"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="oRN-W6-TVF" firstAttribute="width" secondItem="rB7-Ct-Mq7" secondAttribute="width" id="2pR-3D-RYU"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="3Mu-7j-flw" secondAttribute="trailing" id="W3d-d0-w2v"/>
<constraint firstItem="rB7-Ct-Mq7" firstAttribute="leading" secondItem="sX2-Xu-9f5" secondAttribute="leading" id="aH4-6p-pgC"/>
<constraint firstItem="1lx-d3-6c2" firstAttribute="top" secondItem="sX2-Xu-9f5" secondAttribute="top" id="aTB-di-K6F"/>
<constraint firstAttribute="bottom" secondItem="rB7-Ct-Mq7" secondAttribute="bottom" id="dOS-zD-hAw"/>
<constraint firstAttribute="trailing" secondItem="rB7-Ct-Mq7" secondAttribute="trailing" id="iH7-HJ-6RG"/>
<constraint firstAttribute="trailing" secondItem="1lx-d3-6c2" secondAttribute="trailing" id="lLi-mF-pmE"/>
<constraint firstItem="1lx-d3-6c2" firstAttribute="leading" secondItem="sX2-Xu-9f5" secondAttribute="leading" id="rU8-aC-b2j"/>
<constraint firstItem="3Mu-7j-flw" firstAttribute="leading" secondItem="sX2-Xu-9f5" secondAttribute="leading" id="thK-xG-Pvw"/>
<constraint firstItem="3Mu-7j-flw" firstAttribute="top" secondItem="1lx-d3-6c2" secondAttribute="bottom" id="xjU-4b-Ac0"/>
<constraint firstItem="rB7-Ct-Mq7" firstAttribute="top" secondItem="3Mu-7j-flw" secondAttribute="bottom" id="yOc-gk-ilO"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="sX2-Xu-9f5" secondAttribute="trailing" id="9uF-gg-w2a"/>
<constraint firstItem="sX2-Xu-9f5" firstAttribute="width" secondItem="16P-bA-3wP" secondAttribute="width" id="AbT-qq-n6g"/>
<constraint firstItem="sX2-Xu-9f5" firstAttribute="leading" secondItem="16P-bA-3wP" secondAttribute="leading" id="pP0-dc-iec"/>
<constraint firstAttribute="bottom" secondItem="sX2-Xu-9f5" secondAttribute="bottom" id="tUz-rr-k5B"/>
<constraint firstItem="sX2-Xu-9f5" firstAttribute="top" secondItem="16P-bA-3wP" secondAttribute="top" id="yrj-Cm-1Zm"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="16P-bA-3wP" secondAttribute="bottom" id="3Ak-zJ-1Ca"/>
<constraint firstItem="16P-bA-3wP" firstAttribute="top" secondItem="cZY-v6-GjO" secondAttribute="top" id="SV3-Ai-4QS"/>
<constraint firstItem="16P-bA-3wP" firstAttribute="leading" secondItem="cZY-v6-GjO" secondAttribute="leading" id="dlk-85-Zca"/>
<constraint firstItem="16P-bA-3wP" firstAttribute="trailing" secondItem="cZY-v6-GjO" secondAttribute="trailing" id="hFM-5p-Ne1"/>
</constraints>
<viewLayoutGuide key="safeArea" id="cZY-v6-GjO"/>
</view>
<connections>
<outlet property="badgeImageView" destination="0d0-9z-5Ar" id="M0i-8Q-Zal"/>
<outlet property="closeButton" destination="Qdf-kG-wgx" id="yl7-lg-gW6"/>
<outlet property="deviceInformationLabel" destination="FET-ZT-Q69" id="D8n-5b-H4p"/>
<outlet property="deviceStatusImageView" destination="0ME-4K-MWA" id="kgp-dT-Q0P"/>
<outlet property="informationLabel" destination="CTY-XM-iLf" id="qVC-lS-YOY"/>
<outlet property="titleLabel" destination="mXI-Sg-wYe" id="Wrj-QM-srt"/>
<outlet property="untrustedSessionContainerView" destination="oRN-W6-TVF" id="9sc-53-MWY"/>
<outlet property="untrustedSessionInformationLabel" destination="dd2-N5-GEx" id="6TB-Ve-OyC"/>
<outlet property="verifyButton" destination="HiI-uV-Hjw" id="JnR-0Q-aSc"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="8Fe-S5-Pf4" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-175.19999999999999" y="124.58770614692655"/>
</scene>
</scenes>
<resources>
<image name="close_button" width="16" height="16"/>
<image name="encryption_warning" width="16" height="16"/>
</resources>
</document>

View file

@ -0,0 +1,268 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class UserVerificationSessionStatusViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let verifyButtonCornerRadius: CGFloat = 8.0
static let informationTextDefaultFont = UIFont.systemFont(ofSize: 15.0)
static let informationTextBoldFont = UIFont.systemFont(ofSize: 15.0, weight: .medium)
static let deviceNameFont = UIFont.systemFont(ofSize: 17.0, weight: .medium)
static let deviceIdFont = UIFont.systemFont(ofSize: 15.0)
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var badgeImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var closeButton: UIButton!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var deviceStatusImageView: UIImageView!
@IBOutlet private weak var deviceInformationLabel: UILabel!
@IBOutlet private weak var untrustedSessionContainerView: UIView!
@IBOutlet private weak var untrustedSessionInformationLabel: UILabel!
@IBOutlet private weak var verifyButton: UIButton!
// MARK: Private
private var viewModel: UserVerificationSessionStatusViewModelType!
private var theme: Theme!
private var keyboardAvoider: KeyboardAvoider?
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: UserVerificationSessionStatusViewModelType) -> UserVerificationSessionStatusViewController {
let viewController = StoryboardScene.UserVerificationSessionStatusViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.setupViews()
self.vc_removeBackTitle()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.verifyButton.layer.cornerRadius = Constants.verifyButtonCornerRadius
self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.view.backgroundColor = theme.headerBackgroundColor
self.titleLabel.textColor = theme.textPrimaryColor
self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal)
self.informationLabel.textColor = theme.textPrimaryColor
self.untrustedSessionInformationLabel.textColor = theme.textPrimaryColor
self.verifyButton.vc_setBackgroundColor(theme.tintColor, for: .normal)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
self.closeButton.layer.masksToBounds = true
self.verifyButton.layer.masksToBounds = true
}
private func render(viewState: UserVerificationSessionStatusViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(viewData: let sessionStatusViewData):
self.renderLoaded(viewData: sessionStatusViewData)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(viewData: SessionStatusViewData) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
let badgeImage: UIImage
let title: String
if viewData.isDeviceTrusted {
badgeImage = Asset.Images.encryptionTrusted.image
title = "Trusted"
} else {
badgeImage = Asset.Images.encryptionWarning.image
title = "Warning"
}
let unstrustedInformationText: String
let verifyButtonTitle: String
if viewData.isCurrentUser {
unstrustedInformationText = "If you didnt sign in to this session, your account may be compromised."
verifyButtonTitle = "Verify"
} else {
unstrustedInformationText = "Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it."
verifyButtonTitle = "Manually verify"
}
self.badgeImageView.image = badgeImage
self.titleLabel.text = title
self.informationLabel.attributedText = self.buildInformationAttributedText(with: viewData)
self.deviceStatusImageView.image = badgeImage
self.deviceInformationLabel.attributedText = self.builDeviceInfoAttributedText(with: viewData)
self.untrustedSessionInformationLabel.text = unstrustedInformationText
self.verifyButton.setTitle(verifyButtonTitle, for: .normal)
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func buildUserInfoText(with userId: String, userDisplayName: String?) -> String {
let userInfoText: String
if let userDisplayName = userDisplayName {
userInfoText = "\(userDisplayName) (\(userId))"
} else {
userInfoText = userId
}
return userInfoText
}
private func buildInformationAttributedText(with viewData: SessionStatusViewData) -> NSAttributedString {
let informationAttributedText: NSMutableAttributedString = NSMutableAttributedString()
let informationTextDefaultAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor,
.font: Constants.informationTextDefaultFont]
let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor,
.font: Constants.informationTextBoldFont]
let userInfoText = self.buildUserInfoText(with: viewData.userId, userDisplayName: viewData.userDisplayName)
if viewData.isDeviceTrusted {
if viewData.isCurrentUser {
let informationAttributedStringPart1 = NSAttributedString(string: "This session is trusted for secure messaging because you verified it:", attributes: informationTextDefaultAttributes)
informationAttributedText.append(informationAttributedStringPart1)
} else {
let informationAttributedStringPart1 = NSAttributedString(string: "This device is trusted for secure messaging because ", attributes: informationTextDefaultAttributes)
let informationAttributedStringPart2 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes)
let informationAttributedStringPart3 = NSAttributedString(string: " verified it:", attributes: informationTextDefaultAttributes)
informationAttributedText.append(informationAttributedStringPart1)
informationAttributedText.append(informationAttributedStringPart2)
informationAttributedText.append(informationAttributedStringPart3)
}
} else {
if viewData.isCurrentUser {
let informationAttributedStringPart1 = NSAttributedString(string: "Verify this session to mark it as trusted & grant it access to encrypted messages:", attributes: informationTextDefaultAttributes)
informationAttributedText.append(informationAttributedStringPart1)
} else {
let informationAttributedStringPart1 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes)
let informationAttributedStringPart2 = NSAttributedString(string: " signed in using a new device:", attributes: informationTextDefaultAttributes)
informationAttributedText.append(informationAttributedStringPart1)
informationAttributedText.append(informationAttributedStringPart2)
}
}
return informationAttributedText
}
private func builDeviceInfoAttributedText(with viewData: SessionStatusViewData) -> NSAttributedString {
let deviceInfoAttributedText = NSMutableAttributedString()
let deviceInfoAttributedTextPart1 = NSAttributedString(string: "\(viewData.deviceName) ", attributes: [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.deviceNameFont])
let deviceInfoAttributedTextPart2 = NSAttributedString(string: "(\(viewData.deviceId))", attributes: [.foregroundColor: self.theme.textSecondaryColor, .font: Constants.deviceIdFont])
deviceInfoAttributedText.append(deviceInfoAttributedTextPart1)
deviceInfoAttributedText.append(deviceInfoAttributedTextPart2)
return deviceInfoAttributedText
}
// MARK: - Actions
@IBAction private func closeButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .close)
}
@IBAction private func verifyButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .verify)
}
}
// MARK: - UserVerificationSessionStatusViewModelViewDelegate
extension UserVerificationSessionStatusViewController: UserVerificationSessionStatusViewModelViewDelegate {
func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState) {
self.render(viewState: viewSate)
}
}

View file

@ -0,0 +1,95 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct SessionStatusViewData {
let userId: String
let userDisplayName: String?
let isCurrentUser: Bool
let deviceId: String
let deviceName: String
let isDeviceTrusted: Bool
}
enum UserVerificationSessionStatusViewModelError: Error {
case deviceNotFound
}
final class UserVerificationSessionStatusViewModel: UserVerificationSessionStatusViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let userId: String
private let userDisplayName: String?
private let deviceId: String
// MARK: Public
weak var viewDelegate: UserVerificationSessionStatusViewModelViewDelegate?
weak var coordinatorDelegate: UserVerificationSessionStatusViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, userId: String, userDisplayName: String?, deviceId: String) {
self.session = session
self.userId = userId
self.userDisplayName = userDisplayName
self.deviceId = deviceId
}
// MARK: - Public
func process(viewAction: UserVerificationSessionStatusViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .verify:
self.coordinatorDelegate?.userVerificationSessionStatusViewModel(self, wantsToManuallyVerifyDeviceWithId: self.deviceId, for: self.userId)
case .close:
self.coordinatorDelegate?.userVerificationSessionStatusViewModelDidClose(self)
}
}
// MARK: - Private
private func loadData() {
guard let deviceInfo = self.session.crypto.device(withDeviceId: self.deviceId, ofUser: self.userId) else {
self.update(viewState: .error(UserVerificationSessionStatusViewModelError.deviceNotFound))
return
}
let isCurrentUser = self.session.myUser.userId == self.userId
let viewData = SessionStatusViewData(userId: self.userId,
userDisplayName: self.userDisplayName,
isCurrentUser: isCurrentUser,
deviceId: deviceInfo.deviceId,
deviceName: deviceInfo.displayName ?? "",
isDeviceTrusted: deviceInfo.trustLevel.isVerified)
self.update(viewState: .loaded(viewData: viewData))
}
private func update(viewState: UserVerificationSessionStatusViewState) {
self.viewDelegate?.userVerificationSessionStatusViewModel(self, didUpdateViewState: viewState)
}
}

View file

@ -0,0 +1,37 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationSessionStatusViewModelViewDelegate: class {
func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState)
}
protocol UserVerificationSessionStatusViewModelCoordinatorDelegate: class {
func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String)
func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType)
}
/// Protocol describing the view model used by `UserVerificationSessionStatusViewController`
protocol UserVerificationSessionStatusViewModelType {
var viewDelegate: UserVerificationSessionStatusViewModelViewDelegate? { get set }
var coordinatorDelegate: UserVerificationSessionStatusViewModelCoordinatorDelegate? { get set }
func process(viewAction: UserVerificationSessionStatusViewAction)
}

View file

@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh SessionStatus UserVerificationSessionStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// UserVerificationSessionStatusViewController view state
enum UserVerificationSessionStatusViewState {
case loading
case loaded(viewData: SessionStatusViewData)
case error(Error)
}

View file

@ -0,0 +1,79 @@
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
import Reusable
struct UserVerificationSessionStatusViewData {
let deviceId: String
let sessionName: String
let isTrusted: Bool
}
final class UserVerificationSessionStatusCell: UITableViewCell, NibReusable, Themable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var statusImageView: UIImageView!
@IBOutlet private weak var sessionNameLabel: UILabel!
@IBOutlet private weak var statusTextLabel: UILabel!
// MARK: Private
private var viewData: UserVerificationSessionStatusViewData?
private var theme: Theme?
// MARK: - Public
func fill(viewData: UserVerificationSessionStatusViewData) {
self.viewData = viewData
let statusText: String
let statusImage: UIImage
if viewData.isTrusted {
statusImage = Asset.Images.encryptionTrusted.image
statusText = "Trusted"
} else {
statusImage = Asset.Images.encryptionWarning.image
statusText = "Not trusted"
}
self.statusImageView.image = statusImage
self.statusTextLabel.text = statusText
self.sessionNameLabel.text = viewData.sessionName
self.updateStatusTextColor()
}
func update(theme: Theme) {
self.theme = theme
self.backgroundColor = theme.headerBackgroundColor
self.sessionNameLabel.textColor = theme.textPrimaryColor
self.updateStatusTextColor()
}
// MARK: - Private
private func updateStatusTextColor() {
guard let viewData = self.viewData, let theme = self.theme else {
return
}
self.statusTextLabel.textColor = viewData.isTrusted ? theme.tintColor : theme.warningColor
}
}

View file

@ -0,0 +1,78 @@
<?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" useSafeAreas="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="Safe area layout guides" minToolsVersion="9.0"/>
<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"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="37" id="KGk-i7-Jjw" customClass="UserVerificationSessionStatusCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="307" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="307" height="36.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="V5p-pi-DJj">
<rect key="frame" x="0.0" y="0.0" width="307" height="36.5"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="4Ja-9R-o9i" customClass="encryption_warning">
<rect key="frame" x="20" y="11.5" width="14" height="14"/>
<constraints>
<constraint firstAttribute="width" secondItem="4Ja-9R-o9i" secondAttribute="height" multiplier="1:1" id="Ivd-S1-mib"/>
<constraint firstAttribute="width" constant="14" id="n0o-Xb-nnn"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Riot iOS" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MkZ-9n-kCS">
<rect key="frame" x="44" y="10" width="174.5" height="16.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" text="Trusted" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wsp-qe-DLy">
<rect key="frame" x="228.5" y="8" width="58.5" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.01176470588" green="0.70196078429999997" blue="0.50588235290000005" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="MkZ-9n-kCS" firstAttribute="leading" secondItem="4Ja-9R-o9i" secondAttribute="trailing" constant="10" id="29O-OV-c6O"/>
<constraint firstItem="4Ja-9R-o9i" firstAttribute="leading" secondItem="V5p-pi-DJj" secondAttribute="leading" constant="20" id="Gcu-EC-b72"/>
<constraint firstItem="wsp-qe-DLy" firstAttribute="centerY" secondItem="MkZ-9n-kCS" secondAttribute="centerY" id="JQR-uR-OSm"/>
<constraint firstAttribute="bottom" secondItem="MkZ-9n-kCS" secondAttribute="bottom" constant="10" id="Ohk-Zx-SaA"/>
<constraint firstAttribute="trailing" secondItem="wsp-qe-DLy" secondAttribute="trailing" constant="20" id="Onn-ew-tSg"/>
<constraint firstItem="4Ja-9R-o9i" firstAttribute="centerY" secondItem="MkZ-9n-kCS" secondAttribute="centerY" id="U88-nG-bpa"/>
<constraint firstItem="wsp-qe-DLy" firstAttribute="leading" secondItem="MkZ-9n-kCS" secondAttribute="trailing" constant="10" id="VdB-e0-Ru9"/>
<constraint firstItem="MkZ-9n-kCS" firstAttribute="top" secondItem="V5p-pi-DJj" secondAttribute="top" constant="10" id="lx8-Ae-MX8"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="V5p-pi-DJj" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="3rQ-mA-tnY"/>
<constraint firstItem="V5p-pi-DJj" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="PDK-oM-MhJ"/>
<constraint firstAttribute="bottom" secondItem="V5p-pi-DJj" secondAttribute="bottom" id="dj0-s9-48N"/>
<constraint firstAttribute="trailing" secondItem="V5p-pi-DJj" secondAttribute="trailing" id="lp6-TZ-ycQ"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="sessionNameLabel" destination="MkZ-9n-kCS" id="atH-sc-sJx"/>
<outlet property="statusImageView" destination="4Ja-9R-o9i" id="n6o-LV-b3O"/>
<outlet property="statusTextLabel" destination="wsp-qe-DLy" id="4dV-vY-pmR"/>
</connections>
<point key="canvasLocation" x="-72" y="26"/>
</tableViewCell>
</objects>
<resources>
<image name="encryption_warning" width="16" height="16"/>
</resources>
</document>

View file

@ -0,0 +1,70 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var userVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType
private let userVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: UserVerificationSessionsStatusCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, userId: String) {
self.session = session
let userVerificationSessionsStatusViewModel = UserVerificationSessionsStatusViewModel(session: self.session, userId: userId)
let userVerificationSessionsStatusViewController = UserVerificationSessionsStatusViewController.instantiate(with: userVerificationSessionsStatusViewModel)
self.userVerificationSessionsStatusViewModel = userVerificationSessionsStatusViewModel
self.userVerificationSessionsStatusViewController = userVerificationSessionsStatusViewController
}
// MARK: - Public methods
func start() {
self.userVerificationSessionsStatusViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.userVerificationSessionsStatusViewController
}
}
// MARK: - UserVerificationSessionsStatusViewModelCoordinatorDelegate
extension UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusViewModelCoordinatorDelegate {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) {
self.delegate?.userVerificationSessionsStatusCoordinator(self, didSelectDeviceWithId: deviceId, for: userId)
}
func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) {
self.delegate?.userVerificationSessionsStatusCoordinatorDidClose(self)
}
}

View file

@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationSessionsStatusCoordinatorDelegate: class {
func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType)
func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String)
}
/// `UserVerificationSessionsStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol UserVerificationSessionsStatusCoordinatorType: Coordinator, Presentable {
var delegate: UserVerificationSessionsStatusCoordinatorDelegate? { get }
}

View file

@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// UserVerificationSessionsStatusViewController view actions exposed to view model
enum UserVerificationSessionsStatusViewAction {
case loadData
case selectSession(deviceId: String)
case close
}

View file

@ -0,0 +1,119 @@
<?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="le3-e8-C1h">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--User Verification Sessions Status View Controller-->
<scene sceneID="G1j-dD-ArC">
<objects>
<viewController id="le3-e8-C1h" customClass="UserVerificationSessionsStatusViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="IXd-J2-Vex">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kfr-Ho-krK">
<rect key="frame" x="0.0" y="20" width="375" height="108"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NBJ-A1-IP0">
<rect key="frame" x="325" y="20" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="jda-sE-4Uz"/>
<constraint firstAttribute="height" constant="30" id="rPb-uQ-Tyt"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<state key="normal" image="close_button"/>
<connections>
<action selector="closeButtonAction:" destination="le3-e8-C1h" eventType="touchUpInside" id="4JW-Rl-hIk"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="rZH-h1-WTw">
<rect key="frame" x="20" y="23" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="rZH-h1-WTw" secondAttribute="height" multiplier="1:1" id="RgU-aV-cmS"/>
<constraint firstAttribute="height" constant="24" id="v0F-Dp-CmF"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Warning" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fcs-Vl-AtB">
<rect key="frame" x="54" y="23" width="261" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Messages with this user in this room are end-to-end encrypted and cant be read by third parties." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vfc-7p-LLs">
<rect key="frame" x="20" y="62" width="335" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="NBJ-A1-IP0" firstAttribute="leading" secondItem="fcs-Vl-AtB" secondAttribute="trailing" constant="10" id="0cd-68-5tR"/>
<constraint firstItem="fcs-Vl-AtB" firstAttribute="leading" secondItem="rZH-h1-WTw" secondAttribute="trailing" constant="10" id="75Z-TB-Hhj"/>
<constraint firstItem="fcs-Vl-AtB" firstAttribute="centerY" secondItem="NBJ-A1-IP0" secondAttribute="centerY" id="DVH-2n-tXo"/>
<constraint firstItem="Vfc-7p-LLs" firstAttribute="trailing" secondItem="NBJ-A1-IP0" secondAttribute="trailing" id="KQg-I1-5FP"/>
<constraint firstItem="rZH-h1-WTw" firstAttribute="centerY" secondItem="fcs-Vl-AtB" secondAttribute="centerY" id="N8y-sA-Am7"/>
<constraint firstItem="NBJ-A1-IP0" firstAttribute="top" secondItem="Kfr-Ho-krK" secondAttribute="top" constant="20" id="VUJ-Jj-UjR"/>
<constraint firstAttribute="trailing" secondItem="NBJ-A1-IP0" secondAttribute="trailing" constant="20" id="ZH0-2n-XKy"/>
<constraint firstItem="rZH-h1-WTw" firstAttribute="leading" secondItem="Kfr-Ho-krK" secondAttribute="leading" constant="20" id="gPw-ja-veq"/>
<constraint firstItem="Vfc-7p-LLs" firstAttribute="top" secondItem="fcs-Vl-AtB" secondAttribute="bottom" constant="15" id="qh5-nR-1EN"/>
<constraint firstAttribute="bottom" secondItem="Vfc-7p-LLs" secondAttribute="bottom" constant="10" id="t5D-ht-cSc"/>
<constraint firstItem="Vfc-7p-LLs" firstAttribute="leading" secondItem="rZH-h1-WTw" secondAttribute="leading" id="zAt-yf-7HQ"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sessions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uMA-Dt-xTz">
<rect key="frame" x="20" y="133" width="335" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="drr-Gd-5wA">
<rect key="frame" x="0.0" y="167" width="375" height="500"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="dataSource" destination="le3-e8-C1h" id="Xmr-jN-S8s"/>
<outlet property="delegate" destination="le3-e8-C1h" id="Y5J-nA-Ynp"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="drr-Gd-5wA" firstAttribute="leading" secondItem="IXd-J2-Vex" secondAttribute="leading" id="52r-4B-JdK"/>
<constraint firstItem="Kfr-Ho-krK" firstAttribute="trailing" secondItem="Eud-3V-Vqw" secondAttribute="trailing" id="9Zg-EV-cHL"/>
<constraint firstItem="Eud-3V-Vqw" firstAttribute="trailing" secondItem="uMA-Dt-xTz" secondAttribute="trailing" constant="20" id="Amb-5F-Ors"/>
<constraint firstAttribute="trailing" secondItem="drr-Gd-5wA" secondAttribute="trailing" id="Awf-hM-uzJ"/>
<constraint firstItem="drr-Gd-5wA" firstAttribute="top" secondItem="uMA-Dt-xTz" secondAttribute="bottom" constant="10" id="H1a-sI-AbP"/>
<constraint firstAttribute="bottom" secondItem="drr-Gd-5wA" secondAttribute="bottom" id="Nhh-Ps-Upr"/>
<constraint firstItem="Kfr-Ho-krK" firstAttribute="top" secondItem="Eud-3V-Vqw" secondAttribute="top" id="Wxs-Uw-xoA"/>
<constraint firstItem="uMA-Dt-xTz" firstAttribute="leading" secondItem="Eud-3V-Vqw" secondAttribute="leading" constant="20" id="an1-BC-BP0"/>
<constraint firstItem="uMA-Dt-xTz" firstAttribute="top" secondItem="Kfr-Ho-krK" secondAttribute="bottom" constant="5" id="wok-Oq-W0z"/>
<constraint firstItem="Eud-3V-Vqw" firstAttribute="leading" secondItem="Kfr-Ho-krK" secondAttribute="leading" id="xjg-JA-ZUT"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Eud-3V-Vqw"/>
</view>
<connections>
<outlet property="badgeImageImageView" destination="rZH-h1-WTw" id="M2a-7i-OoW"/>
<outlet property="closeButton" destination="NBJ-A1-IP0" id="AYv-b7-fFp"/>
<outlet property="informationLabel" destination="Vfc-7p-LLs" id="em9-jE-1wo"/>
<outlet property="sessionsTableViewTitle" destination="uMA-Dt-xTz" id="vFU-UV-jMO"/>
<outlet property="tableView" destination="drr-Gd-5wA" id="Cs0-pF-wwB"/>
<outlet property="titleLabel" destination="fcs-Vl-AtB" id="iGp-kn-QfJ"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dBA-Zm-evE" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-463.19999999999999" y="-80.50974512743629"/>
</scene>
</scenes>
<resources>
<image name="close_button" width="16" height="16"/>
<image name="encryption_warning" width="16" height="16"/>
</resources>
</document>

View file

@ -0,0 +1,234 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class UserVerificationSessionsStatusViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let estimatedRowHeight: CGFloat = 40.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var badgeImageImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var closeButton: UIButton!
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var sessionsTableViewTitle: UILabel!
@IBOutlet private weak var tableView: UITableView!
// MARK: Private
private var viewModel: UserVerificationSessionsStatusViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityIndicatorPresenter: ActivityIndicatorPresenter!
private var sessionsStatusViewData: [UserVerificationSessionStatusViewData] = []
private var userEncryptionTrustLevel: UserEncryptionTrustLevel = .unknown
// MARK: - Setup
class func instantiate(with viewModel: UserVerificationSessionsStatusViewModelType) -> UserVerificationSessionsStatusViewController {
let viewController = StoryboardScene.UserVerificationSessionsStatusViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.setupViews()
self.vc_removeBackTitle()
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedIndexPath, animated: animated)
}
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal)
self.titleLabel.textColor = theme.textPrimaryColor
self.informationLabel.textColor = theme.textPrimaryColor
self.sessionsTableViewTitle.textColor = theme.textPrimaryColor
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
self.closeButton.layer.masksToBounds = true
self.setupTableView()
self.updateTitleViews()
self.sessionsTableViewTitle.text = "Sessions"
self.informationLabel.text = "Messages with this user in this room are end-to-end encrypted and cant be read by third parties."
}
private func setupTableView() {
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = Constants.estimatedRowHeight
self.tableView.separatorStyle = .none
self.tableView.tableFooterView = UIView()
self.tableView.alwaysBounceVertical = false
self.tableView.register(cellType: UserVerificationSessionStatusCell.self)
}
private func render(viewState: UserVerificationSessionsStatusViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(userTrustLevel: let userTrustLevel, sessionsStatusViewData: let sessionsStatusViewData):
self.renderLoaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.tableView.isUserInteractionEnabled = false
self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData]) {
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
self.tableView.isUserInteractionEnabled = true
self.userEncryptionTrustLevel = userTrustLevel
self.sessionsStatusViewData = sessionsStatusViewData
self.updateTitleViews()
self.tableView.reloadData()
}
private func render(error: Error) {
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
self.tableView.isUserInteractionEnabled = true
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func updateTitleViews() {
let badgeImage: UIImage
let title: String
switch self.userEncryptionTrustLevel {
case .trusted:
badgeImage = Asset.Images.encryptionTrusted.image
title = "Trusted"
case .warning:
badgeImage = Asset.Images.encryptionWarning.image
title = "Warning"
default:
badgeImage = Asset.Images.encryptionNormal.image
title = "Unknown"
}
self.badgeImageImageView.image = badgeImage
self.titleLabel.text = title
}
// MARK: - Actions
@IBAction private func closeButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .close)
}
}
// MARK: - UITableViewDataSource
extension UserVerificationSessionsStatusViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sessionsStatusViewData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: UserVerificationSessionStatusCell.self)
let viewData = self.sessionsStatusViewData[indexPath.row]
cell.update(theme: self.theme)
cell.fill(viewData: viewData)
return cell
}
}
// MARK: - UITableViewDelegate
extension UserVerificationSessionsStatusViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewData = self.sessionsStatusViewData[indexPath.row]
self.viewModel.process(viewAction: .selectSession(deviceId: viewData.deviceId))
}
}
// MARK: - UserVerificationSessionsStatusViewModelViewDelegate
extension UserVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewModelViewDelegate {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) {
self.render(viewState: viewSate)
}
}

View file

@ -0,0 +1,148 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum UserVerificationSessionsStatusViewModelError: Error {
case unknown
}
final class UserVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let userId: String
private var currentOperation: MXHTTPOperation?
private var userTrustLevel: UserEncryptionTrustLevel
// MARK: Public
weak var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate?
weak var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, userId: String) {
self.session = session
self.userId = userId
self.userTrustLevel = .unknown
}
deinit {
self.currentOperation?.cancel()
}
// MARK: - Public
func process(viewAction: UserVerificationSessionsStatusViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .selectSession(deviceId: let deviceId):
self.coordinatorDelegate?.userVerificationSessionsStatusViewModel(self, didSelectDeviceWithId: deviceId, for: self.userId)
case .close:
self.coordinatorDelegate?.userVerificationSessionsStatusViewModelDidClose(self)
}
}
// MARK: - Private
private func loadData() {
let sessionsStatusViewData = self.getSessionStatusViewDataListFromCache(for: self.userId)
self.update(viewState: .loaded(userTrustLevel: self.userTrustLevel, sessionsStatusViewData: sessionsStatusViewData))
self.fetchSessionStatus()
}
private func update(viewState: UserVerificationSessionsStatusViewState) {
self.viewDelegate?.userVerificationSessionsStatusViewModel(self, didUpdateViewState: viewState)
}
private func fetchSessionStatus() {
self.update(viewState: .loading)
self.currentOperation = self.getSessionStatusViewDataList(for: self.userId) { result in
switch result {
case .success(let sessionsStatusViewData):
let isUserTrusted = sessionsStatusViewData.contains(where: { sessionsStatusViewData -> Bool in
return sessionsStatusViewData.isTrusted == false
}) == false
let userTrustLevel: UserEncryptionTrustLevel = isUserTrusted ? .trusted : .warning
self.update(viewState: .loaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData))
case .failure(let error):
self.update(viewState: .error(error))
}
}
}
private func getSessionStatusViewDataListFromCache(for userId: String) -> [UserVerificationSessionStatusViewData] {
let deviceInfoList = self.getDevicesFromCache(for: self.userId)
return self.sessionStatusViewDataList(from: deviceInfoList)
}
private func getDevicesFromCache(for userId: String) -> [MXDeviceInfo] {
guard let deviceInfoMap = self.session.crypto.devices(forUser: self.userId) else {
return []
}
return Array(deviceInfoMap.values)
}
@discardableResult
private func getSessionStatusViewDataList(for userId: String, completion: @escaping (Result<[UserVerificationSessionStatusViewData], Error>) -> Void) -> MXHTTPOperation? {
let httpOperation: MXHTTPOperation?
httpOperation = self.session.crypto.downloadKeys([self.userId], forceDownload: false, success: { ( usersDeviceMap: MXUsersDevicesMap<MXDeviceInfo>?, usersCrossSigningMap: [String : MXCrossSigningInfo]?) in
let sessionsViewData: [UserVerificationSessionStatusViewData]
if let usersDeviceMap = usersDeviceMap, let userDeviceInfoMap = Array(usersDeviceMap.map.values).first {
let deviceInfoList = Array(userDeviceInfoMap.values)
sessionsViewData = self.sessionStatusViewDataList(from: deviceInfoList)
} else {
sessionsViewData = []
}
completion(.success(sessionsViewData))
}, failure: { error in
let finalError = error ?? UserVerificationSessionsStatusViewModelError.unknown
completion(.failure(finalError))
})
return httpOperation
}
private func sessionStatusViewData(from deviceInfo: MXDeviceInfo) -> UserVerificationSessionStatusViewData {
return UserVerificationSessionStatusViewData(deviceId: deviceInfo.deviceId, sessionName: deviceInfo.displayName ?? "", isTrusted: deviceInfo.trustLevel.isVerified)
}
private func sessionStatusViewDataList(from deviceInfoList: [MXDeviceInfo]) -> [UserVerificationSessionStatusViewData] {
return deviceInfoList.map { (deviceInfo) -> UserVerificationSessionStatusViewData in
return self.sessionStatusViewData(from: deviceInfo)
}
}
}

View file

@ -0,0 +1,37 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationSessionsStatusViewModelViewDelegate: class {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState)
}
protocol UserVerificationSessionsStatusViewModelCoordinatorDelegate: class {
func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String)
func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType)
}
/// Protocol describing the view model used by `UserVerificationSessionsStatusViewController`
protocol UserVerificationSessionsStatusViewModelType {
var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate? { get set }
var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate? { get set }
func process(viewAction: UserVerificationSessionsStatusViewAction)
}

View file

@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh UserVerification UserVerificationSessionsStatus
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// UserVerificationSessionsStatusViewController view state
enum UserVerificationSessionsStatusViewState {
case loading
case loaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData])
case error(Error)
}

View file

@ -0,0 +1,77 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class UserVerificationStartCoordinator: UserVerificationStartCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let roomMember: MXRoomMember
private var userVerificationStartViewModel: UserVerificationStartViewModelType
private let userVerificationStartViewController: UserVerificationStartViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: UserVerificationStartCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, roomMember: MXRoomMember) {
self.session = session
self.roomMember = roomMember
let userVerificationStartViewModel = UserVerificationStartViewModel(session: self.session, roomMember: self.roomMember)
let userVerificationStartViewController = UserVerificationStartViewController.instantiate(with: userVerificationStartViewModel)
self.userVerificationStartViewModel = userVerificationStartViewModel
self.userVerificationStartViewController = userVerificationStartViewController
}
// MARK: - Public methods
func start() {
self.userVerificationStartViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.userVerificationStartViewController
}
}
// MARK: - UserVerificationStartViewModelCoordinatorDelegate
extension UserVerificationStartCoordinator: UserVerificationStartViewModelCoordinatorDelegate {
func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType) {
self.delegate?.userVerificationStartCoordinatorDidCancel(self)
}
func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithIncomingTransaction transaction: MXSASTransaction) {
self.delegate?.userVerificationStartCoordinator(self, didCompleteWithOutgoingTransaction: transaction)
}
func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.userVerificationStartCoordinator(self, didTransactionCancelled: transaction)
}
}

View file

@ -0,0 +1,32 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationStartCoordinatorDelegate: class {
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction)
func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction)
func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType)
}
/// `UserVerificationStartCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol UserVerificationStartCoordinatorType: Coordinator, Presentable {
var delegate: UserVerificationStartCoordinatorDelegate? { get }
}

View file

@ -0,0 +1,26 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// UserVerificationStartViewController view actions exposed to view model
enum UserVerificationStartViewAction {
case loadData
case startVerification
case cancel
}

View file

@ -0,0 +1,101 @@
<?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="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--User Verification Start View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="UserVerificationStartViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dQD-Vs-7h6">
<rect key="frame" x="0.0" y="254" width="375" height="179"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="For extra security, verify @user:matrix.org by checking a one-time code on both your devices." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="b2K-LP-jqv">
<rect key="frame" x="20" y="10" width="335" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Waiting for User…" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0QC-yi-0hN">
<rect key="frame" x="20" y="78" width="335" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3sZ-h5-05D">
<rect key="frame" x="20" y="66" width="335" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCLoginButton"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="2qO-rj-st6"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="8kK-yb-srh"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Start Verification">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="startVerificationButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="3Nd-Km-ucJ"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="To be secure, do this in person or use another way to communicate." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vli-IZ-UjP">
<rect key="frame" x="20" y="130" width="335" height="29"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="3sZ-h5-05D" firstAttribute="centerX" secondItem="dQD-Vs-7h6" secondAttribute="centerX" id="2OM-Zh-gnO"/>
<constraint firstAttribute="trailing" secondItem="0QC-yi-0hN" secondAttribute="trailing" constant="20" id="70Z-qZ-AG4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="3sZ-h5-05D" secondAttribute="trailing" constant="20" id="ABg-Iy-IdN"/>
<constraint firstItem="Vli-IZ-UjP" firstAttribute="top" secondItem="3sZ-h5-05D" secondAttribute="bottom" constant="20" id="BIE-UM-I0Z"/>
<constraint firstItem="3sZ-h5-05D" firstAttribute="top" secondItem="b2K-LP-jqv" secondAttribute="bottom" constant="20" id="HCf-2F-jBr"/>
<constraint firstItem="Vli-IZ-UjP" firstAttribute="leading" secondItem="dQD-Vs-7h6" secondAttribute="leading" constant="20" id="Nz0-mv-fF5"/>
<constraint firstItem="3sZ-h5-05D" firstAttribute="width" secondItem="dQD-Vs-7h6" secondAttribute="width" priority="250" id="RuV-mq-5JZ"/>
<constraint firstAttribute="trailing" secondItem="Vli-IZ-UjP" secondAttribute="trailing" constant="20" id="XXe-5j-SxE"/>
<constraint firstAttribute="bottom" secondItem="Vli-IZ-UjP" secondAttribute="bottom" constant="20" id="Yfh-kG-UKJ"/>
<constraint firstItem="b2K-LP-jqv" firstAttribute="leading" secondItem="dQD-Vs-7h6" secondAttribute="leading" constant="20" id="dgE-c5-D4E"/>
<constraint firstItem="0QC-yi-0hN" firstAttribute="leading" secondItem="dQD-Vs-7h6" secondAttribute="leading" constant="20" id="g4e-WC-LQ7"/>
<constraint firstItem="0QC-yi-0hN" firstAttribute="centerY" secondItem="3sZ-h5-05D" secondAttribute="centerY" id="hqE-9r-yhx"/>
<constraint firstAttribute="height" priority="250" id="j1W-Y7-dYc"/>
<constraint firstItem="3sZ-h5-05D" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dQD-Vs-7h6" secondAttribute="leading" constant="20" id="oP3-BI-kYI"/>
<constraint firstAttribute="trailing" secondItem="b2K-LP-jqv" secondAttribute="trailing" constant="20" id="uJ5-fk-7Xw"/>
<constraint firstItem="b2K-LP-jqv" firstAttribute="top" secondItem="dQD-Vs-7h6" secondAttribute="top" constant="10" id="vlb-Yg-HWD"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="dQD-Vs-7h6" firstAttribute="centerY" secondItem="bFg-jh-JZB" secondAttribute="centerY" id="GmI-7r-ejI"/>
<constraint firstItem="dQD-Vs-7h6" firstAttribute="trailing" secondItem="bFg-jh-JZB" secondAttribute="trailing" id="XX2-fS-8cb"/>
<constraint firstItem="dQD-Vs-7h6" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="yKt-js-MnW"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="additionalInformationLabel" destination="Vli-IZ-UjP" id="4UB-LL-Om3"/>
<outlet property="informationLabel" destination="b2K-LP-jqv" id="O4J-7t-Dn6"/>
<outlet property="startVerificationButton" destination="3sZ-h5-05D" id="vNi-m9-x7g"/>
<outlet property="verificationWaitingLabel" destination="0QC-yi-0hN" id="Qoz-cV-Su7"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198" y="-647"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,229 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class UserVerificationStartViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let verifyButtonCornerRadius: CGFloat = 8.0
static let informationTextDefaultFont = UIFont.systemFont(ofSize: 15.0)
static let informationTextBoldFont = UIFont.systemFont(ofSize: 15.0, weight: .medium)
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var informationLabel: UILabel!
@IBOutlet private weak var startVerificationButton: UIButton!
@IBOutlet private weak var verificationWaitingLabel: UILabel!
@IBOutlet private weak var additionalInformationLabel: UILabel!
// MARK: Private
private var viewModel: UserVerificationStartViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
// MARK: - Setup
class func instantiate(with viewModel: UserVerificationStartViewModelType) -> UserVerificationStartViewController {
let viewController = StoryboardScene.UserVerificationStartViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "Verify user"
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.startVerificationButton.layer.cornerRadius = Constants.verifyButtonCornerRadius
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.informationLabel.textColor = theme.textPrimaryColor
self.startVerificationButton.vc_setBackgroundColor(theme.tintColor, for: .normal)
self.verificationWaitingLabel.textColor = theme.textSecondaryColor
self.additionalInformationLabel.textColor = theme.textSecondaryColor
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.startVerificationButton.layer.masksToBounds = true
self.startVerificationButton.setTitle("Start verification", for: .normal)
}
private func render(viewState: UserVerificationStartViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(let viewData):
self.renderLoaded(viewData: viewData)
case .error(let error):
self.render(error: error)
case .verificationPending:
self.renderVerificationPending()
case .cancelled(let reason):
self.renderCancelled(reason: reason)
case .cancelledByMe(let reason):
self.renderCancelledByMe(reason: reason)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(viewData: UserVerificationStartViewData) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.informationLabel.attributedText = self.buildInformationAttributedText(with: viewData.userId)
self.verificationWaitingLabel.text = self.buildVerificationWaitingText(with: viewData)
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
private func renderVerificationPending() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.startVerificationButton.isHidden = true
self.verificationWaitingLabel.isHidden = false
}
private func renderCancelled(reason: MXTransactionCancelCode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
self.viewModel.process(viewAction: .cancel)
}
}
private func renderCancelledByMe(reason: MXTransactionCancelCode) {
if reason.value != MXTransactionCancelCode.user().value {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) {
self.viewModel.process(viewAction: .cancel)
}
} else {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
}
private func buildInformationAttributedText(with userId: String) -> NSAttributedString {
let informationAttributedText: NSMutableAttributedString = NSMutableAttributedString()
let informationTextDefaultAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor,
.font: Constants.informationTextDefaultFont]
let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor,
.font: Constants.informationTextBoldFont]
let informationAttributedStringPart1 = NSAttributedString(string: "For extra security, verify ", attributes: informationTextDefaultAttributes)
let informationAttributedStringPart2 = NSAttributedString(string: userId, attributes: informationTextBoldAttributes)
let informationAttributedStringPart3 = NSAttributedString(string: " by checking a one-time code on both your devices.", attributes: informationTextDefaultAttributes)
informationAttributedText.append(informationAttributedStringPart1)
informationAttributedText.append(informationAttributedStringPart2)
informationAttributedText.append(informationAttributedStringPart3)
return informationAttributedText
}
private func buildVerificationWaitingText(with viewData: UserVerificationStartViewData) -> String {
let userName = viewData.userDisplayName ?? viewData.userId
return "Waiting for \(userName)"
}
// MARK: - Actions
@IBAction private func startVerificationButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .startVerification)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
}
// MARK: - UserVerificationStartViewModelViewDelegate
extension UserVerificationStartViewController: UserVerificationStartViewModelViewDelegate {
func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState) {
self.render(viewState: viewSate)
}
}

View file

@ -0,0 +1,200 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum UserVerificationStartViewModelError: Error {
case keyVerificationRequestExpired
}
struct UserVerificationStartViewData {
let userId: String
let userDisplayName: String?
let userAvatarURL: String?
}
final class UserVerificationStartViewModel: UserVerificationStartViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let roomMember: MXRoomMember
private let verificationManager: MXDeviceVerificationManager
private var keyVerificationRequest: MXKeyVerificationRequest?
private var viewData: UserVerificationStartViewData {
return UserVerificationStartViewData(userId: self.roomMember.userId, userDisplayName: self.roomMember.displayname, userAvatarURL: self.roomMember.avatarUrl)
}
// MARK: Public
weak var viewDelegate: UserVerificationStartViewModelViewDelegate?
weak var coordinatorDelegate: UserVerificationStartViewModelCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, roomMember: MXRoomMember) {
self.session = session
self.verificationManager = session.crypto.deviceVerificationManager
self.roomMember = roomMember
}
deinit {
}
// MARK: - Public
func process(viewAction: UserVerificationStartViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .startVerification:
self.startVerification()
case .cancel:
self.cancelKeyVerificationRequest()
self.coordinatorDelegate?.userVerificationStartViewModelDidCancel(self)
}
}
// MARK: - Private
private func loadData() {
self.update(viewState: .loaded(self.viewData))
}
private func startVerification() {
self.update(viewState: .verificationPending)
self.verificationManager.requestVerificationByDM(withUserId: self.roomMember.userId,
roomId: nil,
fallbackText: "",
methods: [MXKeyVerificationMethodSAS],
success: { [weak self] (keyVerificationRequest) in
guard let self = self else {
return
}
self.keyVerificationRequest = keyVerificationRequest
self.update(viewState: .loaded(self.viewData))
self.registerKeyVerificationDidChangeNotification(keyVerificationRequest: keyVerificationRequest)
self.registerTransactionDidStateChangeNotification()
}, failure: { [weak self] error in
self?.update(viewState: .error(error))
})
}
private func update(viewState: UserVerificationStartViewState) {
self.viewDelegate?.userVerificationStartViewModel(self, didUpdateViewState: viewState)
}
private func cancelKeyVerificationRequest() {
guard let keyVerificationRequest = self.keyVerificationRequest else {
return
}
keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil)
}
// MARK: - MXDeviceVerificationTransactionDidChange
private func registerTransactionDidStateChangeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXDeviceVerificationTransactionDidChange, object: nil)
}
private func unregisterTransactionDidStateChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil)
}
@objc private func transactionDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXIncomingSASTransaction else {
return
}
guard let keyVerificationRequest = self.keyVerificationRequest,
let transactionDMEventId = transaction.dmEventId,
keyVerificationRequest.requestId == transactionDMEventId else {
return
}
switch transaction.state {
case MXSASTransactionStateShowSAS:
self.unregisterTransactionDidStateChangeNotification()
self.coordinatorDelegate?.userVerificationStartViewModel(self, didCompleteWithIncomingTransaction: transaction)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else {
return
}
self.unregisterTransactionDidStateChangeNotification()
self.update(viewState: .cancelledByMe(reason))
default:
break
}
}
// MARK: - MXDeviceVerificationTransactionDidChange
private func registerKeyVerificationDidChangeNotification(keyVerificationRequest: MXKeyVerificationRequest) {
NotificationCenter.default.addObserver(self, selector: #selector(keyVerificationRequestDidChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: keyVerificationRequest)
}
private func unregisterKeyVerificationDidChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil)
}
@objc private func keyVerificationRequestDidChange(notification: Notification) {
guard let keyVerificationRequest = notification.object as? MXKeyVerificationByDMRequest else {
return
}
guard let currentKeyVerificationRequest = self.keyVerificationRequest, keyVerificationRequest.requestId == currentKeyVerificationRequest.requestId else {
return
}
switch keyVerificationRequest.state {
case MXKeyVerificationRequestStateAccepted:
self.unregisterKeyVerificationDidChangeNotification()
case MXKeyVerificationRequestStateCancelled:
guard let reason = keyVerificationRequest.reasonCancelCode else {
return
}
self.unregisterKeyVerificationDidChangeNotification()
self.update(viewState: .cancelled(reason))
case MXKeyVerificationRequestStateCancelledByMe:
guard let reason = keyVerificationRequest.reasonCancelCode else {
return
}
self.unregisterKeyVerificationDidChangeNotification()
self.update(viewState: .cancelledByMe(reason))
case MXKeyVerificationRequestStateExpired:
self.unregisterKeyVerificationDidChangeNotification()
self.update(viewState: .error(UserVerificationStartViewModelError.keyVerificationRequestExpired))
default:
break
}
}
}

View file

@ -0,0 +1,41 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationStartViewModelViewDelegate: class {
func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState)
}
protocol UserVerificationStartViewModelCoordinatorDelegate: class {
func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithIncomingTransaction transaction: MXSASTransaction)
func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction)
func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType)
}
/// Protocol describing the view model used by `UserVerificationStartViewController`
protocol UserVerificationStartViewModelType {
var viewDelegate: UserVerificationStartViewModelViewDelegate? { get set }
var coordinatorDelegate: UserVerificationStartViewModelCoordinatorDelegate? { get set }
func process(viewAction: UserVerificationStartViewAction)
}

View file

@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh Start UserVerificationStart
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// UserVerificationStartViewController view state
enum UserVerificationStartViewState {
case loading
case loaded(UserVerificationStartViewData)
case verificationPending
case cancelled(MXTransactionCancelCode)
case cancelledByMe(MXTransactionCancelCode)
case error(Error)
}

View file

@ -0,0 +1,143 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh UserVerification UserVerification
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
@objcMembers
final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorType {
// MARK: - Properties
// MARK: Private
private let presenter: Presentable
private let navigationRouter: NavigationRouterType
private let session: MXSession
private let userId: String
private let userDisplayName: String?
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: UserVerificationCoordinatorDelegate?
// MARK: - Setup
init(presenter: Presentable, session: MXSession, userId: String, userDisplayName: String?) {
self.presenter = presenter
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
self.session = session
self.userId = userId
self.userDisplayName = userDisplayName
}
// MARK: - Public methods
func start() {
// Do not start again if existing coordinators are presented
guard self.childCoordinators.isEmpty else {
return
}
let rootCoordinator = UserVerificationSessionsStatusCoordinator(session: self.session, userId: self.userId)
rootCoordinator.delegate = self
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
self.navigationRouter.setRootModule(rootCoordinator, hideNavigationBar: true, animated: false, popCompletion: {
self.remove(childCoordinator: rootCoordinator)
})
let rootViewController = self.navigationRouter.toPresentable()
rootViewController.modalPresentationStyle = .formSheet
self.presenter.toPresentable().present(rootViewController, animated: true, completion: nil)
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
}
// MARK: - Private methods
private func presentSessionStatus(with deviceId: String, for userId: String, userDisplayName: String?) {
let coordinator = UserVerificationSessionStatusCoordinator(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId)
coordinator.delegate = self
coordinator.start()
self.navigationRouter.push(coordinator, animated: true) {
self.remove(childCoordinator: coordinator)
}
}
private func presentDeviceVerification(for deviceId: String) {
let deviceVerificationCoordinator = DeviceVerificationCoordinator(navigationRouter: self.navigationRouter, session: self.session, userId: self.userId, otherDeviceId: deviceId)
deviceVerificationCoordinator.delegate = self
deviceVerificationCoordinator.start()
self.add(childCoordinator: deviceVerificationCoordinator)
self.navigationRouter.push(deviceVerificationCoordinator, animated: true, popCompletion: {
self.remove(childCoordinator: deviceVerificationCoordinator)
})
}
}
// MARK: - UserVerificationSessionsStatusCoordinatorDelegate
extension UserVerificationCoordinator: UserVerificationSessionsStatusCoordinatorDelegate {
func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType) {
self.presenter.toPresentable().dismiss(animated: true) {
self.remove(childCoordinator: coordinator)
}
}
func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String) {
self.presentSessionStatus(with: deviceId, for: userId, userDisplayName: self.userDisplayName)
}
}
// MARK: - UserVerificationSessionStatusCoordinatorDelegate
extension UserVerificationCoordinator: UserVerificationSessionStatusCoordinatorDelegate {
func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) {
self.presentDeviceVerification(for: deviceId)
}
func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType) {
self.presenter.toPresentable().dismiss(animated: true) {
self.remove(childCoordinator: coordinator)
}
}
}
// MARK: - UserVerificationCoordinatorDelegate
extension UserVerificationCoordinator: DeviceVerificationCoordinatorDelegate {
func deviceVerificationCoordinatorDidComplete(_ coordinator: DeviceVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) {
self.presenter.toPresentable().dismiss(animated: true) {
self.remove(childCoordinator: coordinator)
}
}
}

View file

@ -0,0 +1,66 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh UserVerification UserVerification
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
@objc protocol UserVerificationCoordinatorBridgePresenterDelegate {
func userVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: UserVerificationCoordinatorBridgePresenter)
}
/// UserVerificationCoordinatorBridgePresenter enables to start UserVerificationCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
@objcMembers
final class UserVerificationCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let presenter: Presentable
private let session: MXSession
private let userId: String
private let userDisplayName: String?
private var coordinator: Coordinator?
// MARK: Public
weak var delegate: UserVerificationCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(presenter: UIViewController, session: MXSession, userId: String, userDisplayName: String?) {
self.presenter = presenter
self.session = session
self.userId = userId
self.userDisplayName = userDisplayName
super.init()
}
// MARK: - Public
func start() {
self.present()
}
func present() {
let userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName)
userVerificationCoordinator.start()
self.coordinator = userVerificationCoordinator
}
}

View file

@ -0,0 +1,28 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh UserVerification UserVerification
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol UserVerificationCoordinatorDelegate: class {
func userVerificationCoordinatorDidComplete(_ coordinator: UserVerificationCoordinatorType)
}
/// `UserVerificationCoordinatorType` is a protocol describing a Coordinator that handle user verification navigation flow.
protocol UserVerificationCoordinatorType: Coordinator, Presentable {
var delegate: UserVerificationCoordinatorDelegate? { get }
}

View file

@ -24,10 +24,13 @@ final class NavigationRouter: NSObject, NavigationRouterType {
// MARK: Private
private var completions: [UIViewController : () -> Void]
private let navigationController: UINavigationController
// MARK: Public
private let navigationController: UINavigationController
var modules: [Presentable] {
return navigationController.viewControllers
}
// MARK: - Setup

View file

@ -61,6 +61,9 @@ protocol NavigationRouterType: class, Presentable {
///
/// - Parameter animated: Specify true to animate the transition.
func popModule(animated: Bool)
/// Returns the modules that are currently in the navigation stack
var modules: [Presentable] { get }
}
// `NavigationRouterType` default implementation

View file

@ -17,3 +17,4 @@
#import "AppDelegate.h"
#import "RoomBubbleCellData.h"
#import "MXKRoomBubbleTableViewCell+Riot.h"
#import "UserEncryptionTrustLevel.h"