Merge pull request #2706 from vector-im/riot_2606

Settings: Add a Discovery section
This commit is contained in:
SBiOSoftWhare
2019-09-11 16:37:14 +02:00
committed by GitHub
25 changed files with 1850 additions and 5 deletions
+1
View File
@@ -19,6 +19,7 @@ Improvements:
* Privacy: Settings: Allow adding 3pids when no IS (#2659).
* Privacy: Allow password reset when no IS (#2658).
* Privacy: Allow email registration when no IS (#2657).
* Privacy: Settings: Add a Discovery section (#2606).
Changes in 0.9.2 (2019-08-08)
===============================================
+72
View File
@@ -157,6 +157,11 @@
B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */; };
B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872221F098F0003554A5 /* ActivityIndicatorView.swift */; };
B120863722EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */; };
B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */; };
B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */; };
B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */; };
B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */; };
B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.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 */; };
@@ -178,6 +183,15 @@
B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142D22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift */; };
B152C73122DF561E0041315A /* EmojiServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B152C73022DF561E0041315A /* EmojiServiceTests.swift */; };
B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = B152C73422DF599B0041315A /* apple_emojis_data.json */; };
B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */; };
B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */; };
B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */; };
B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9A23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift */; };
B157FAA323264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9B23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift */; };
B157FAA423264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B157FA9C23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard */; };
B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9D23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift */; };
B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9E23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift */; };
B157FAA823264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FAA723264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift */; };
B1664BC520F4E67600808783 /* FallbackViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1664BAD20F4E67500808783 /* FallbackViewController.xib */; };
B1664BC620F4E67600808783 /* FallbackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1664BAE20F4E67500808783 /* FallbackViewController.m */; };
B1664BC720F4E67600808783 /* SharePresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1664BB220F4E67500808783 /* SharePresentingViewController.m */; };
@@ -787,6 +801,11 @@
B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = "<group>"; };
B110872221F098F0003554A5 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = "<group>"; };
B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryBridgeCoordinatorPresenter.swift; sourceTree = "<group>"; };
B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryTableViewSection.swift; sourceTree = "<group>"; };
B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewModel.swift; sourceTree = "<group>"; };
B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewModelType.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@@ -809,6 +828,15 @@
B152C72D22DF55D80041315A /* RiotTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RiotTests-Bridging-Header.h"; sourceTree = "<group>"; };
B152C73022DF561E0041315A /* EmojiServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceTests.swift; sourceTree = "<group>"; };
B152C73422DF599B0041315A /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = "<group>"; };
B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinator.swift; sourceTree = "<group>"; };
B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewModelType.swift; sourceTree = "<group>"; };
B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinatorType.swift; sourceTree = "<group>"; };
B157FA9A23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewAction.swift; sourceTree = "<group>"; };
B157FA9B23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewState.swift; sourceTree = "<group>"; };
B157FA9C23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SettingsDiscoveryThreePidDetailsViewController.storyboard; sourceTree = "<group>"; };
B157FA9D23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewModel.swift; sourceTree = "<group>"; };
B157FA9E23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewController.swift; sourceTree = "<group>"; };
B157FAA723264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B1664BAD20F4E67500808783 /* FallbackViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FallbackViewController.xib; sourceTree = "<group>"; };
B1664BAE20F4E67500808783 /* FallbackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FallbackViewController.m; sourceTree = "<group>"; };
B1664BAF20F4E67500808783 /* FallbackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FallbackViewController.h; sourceTree = "<group>"; };
@@ -1903,6 +1931,19 @@
path = ActivityIndicator;
sourceTree = "<group>";
};
B125FE19231D5B5600B72806 /* Discovery */ = {
isa = PBXGroup;
children = (
B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */,
B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */,
B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */,
B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */,
B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */,
B157FA9623264AE800EBFBD4 /* ThreePidDetails */,
);
path = Discovery;
sourceTree = "<group>";
};
B14F142522144F6400FA0595 /* RecoveryKey */ = {
isa = PBXGroup;
children = (
@@ -1951,6 +1992,22 @@
path = Data;
sourceTree = "<group>";
};
B157FA9623264AE800EBFBD4 /* ThreePidDetails */ = {
isa = PBXGroup;
children = (
B157FAA723264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift */,
B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */,
B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */,
B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */,
B157FA9D23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift */,
B157FA9A23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift */,
B157FA9B23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift */,
B157FA9E23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift */,
B157FA9C23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard */,
);
path = ThreePidDetails;
sourceTree = "<group>";
};
B1664BAB20F4E67500808783 /* Modules */ = {
isa = PBXGroup;
children = (
@@ -2272,6 +2329,7 @@
B1B5567E20EE6C4C00210D55 /* SettingsViewController.m */,
B1B5578120EF564900210D55 /* Views */,
32BF994D21FA1C6300698084 /* KeyBackup */,
B125FE19231D5B5600B72806 /* Discovery */,
B1B5567B20EE6C4C00210D55 /* Language */,
B1B5567820EE6C4C00210D55 /* PhoneCountry */,
B1B5568020EE6C4C00210D55 /* DeactivateAccount */,
@@ -3858,6 +3916,7 @@
B1B5597020EFA85D00210D55 /* EncryptionInfoView.xib in Resources */,
B1B558BC20EF768F00210D55 /* RoomMembershipBubbleCell.xib in Resources */,
B1B5571F20EE6C4D00210D55 /* ContactDetailsViewController.xib in Resources */,
B157FAA423264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard in Resources */,
B1B558E220EF768F00210D55 /* RoomIncomingTextMsgBubbleCell.xib in Resources */,
B105778B221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard in Resources */,
B1664DA220F4F95800808783 /* Localizable.strings in Resources */,
@@ -4200,6 +4259,7 @@
324A2053225FC571004FE8B0 /* DeviceVerificationIncomingViewModel.swift in Sources */,
B1B557A120EF58AD00210D55 /* ContactTableViewCell.m in Sources */,
F083BE021E7009ED00A9B29C /* AvatarGenerator.m in Sources */,
B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */,
B1B5573A20EE6C4D00210D55 /* GroupRoomsViewController.m in Sources */,
B1B558F920EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m in Sources */,
B1FDF56021F5FE5500BA3834 /* KeyBackupSetupPassphraseViewAction.swift in Sources */,
@@ -4218,6 +4278,7 @@
B1B5574B20EE6C4D00210D55 /* MediaAlbumContentViewController.m in Sources */,
B1B5598820EFC3E000210D55 /* WidgetManager.m in Sources */,
B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */,
B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */,
B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */,
B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */,
B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */,
@@ -4262,6 +4323,7 @@
B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */,
B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */,
B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
B157FAA823264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift in Sources */,
B169330B20F3CA3A00746532 /* Contact.m in Sources */,
B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */,
B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */,
@@ -4291,6 +4353,7 @@
B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */,
B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */,
32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */,
B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */,
B1B5594720EF7BD000210D55 /* RoomCollectionViewCell.m in Sources */,
B10CFBC32268D99D00A5842E /* JitsiService.swift in Sources */,
B1B558C120EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */,
@@ -4351,6 +4414,7 @@
B1B5582C20EF666100210D55 /* DirectoryRecentTableViewCell.m in Sources */,
B1B558E420EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */,
B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */,
B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */,
3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */,
B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */,
B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */,
@@ -4362,7 +4426,9 @@
B1DCC63922E85E9A00625807 /* EmojiMartStore.swift in Sources */,
B1B5590620EF768F00210D55 /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m in Sources */,
B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */,
B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */,
F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */,
B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */,
B1DCC62422E60CA900625807 /* EmojiPickerCategoryViewData.swift in Sources */,
324A2056225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift in Sources */,
B16932F720F3C50E00746532 /* RecentsDataSource.m in Sources */,
@@ -4401,6 +4467,8 @@
B1B557DE20EF5FBB00210D55 /* FilesSearchTableViewCell.m in Sources */,
B1B5574020EE6C4D00210D55 /* SegmentedViewController.m in Sources */,
B1B5599320EFC5E400210D55 /* DecryptionFailure.m in Sources */,
B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */,
B157FAA323264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift in Sources */,
B1098BF921ECFE65000DDA48 /* KeyBackupSetupCoordinator.swift in Sources */,
B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */,
B1098BFE21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModelType.swift in Sources */,
@@ -4475,6 +4543,7 @@
B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */,
B1B558F020EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */,
926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */,
B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */,
B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */,
B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */,
3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */,
@@ -4491,6 +4560,7 @@
B1098BFF21ECFE65000DDA48 /* PasswordStrengthView.swift in Sources */,
B1B558D220EF768F00210D55 /* RoomEncryptedDataBubbleCell.m in Sources */,
B1B558FA20EF768F00210D55 /* RoomMembershipBubbleCell.m in Sources */,
B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */,
3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */,
B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */,
B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */,
@@ -4551,6 +4621,7 @@
3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */,
B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */,
B1B558F820EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */,
32242F0921E8B05F00725742 /* UIColor.swift in Sources */,
B16932E720F3C37100746532 /* HomeMessagesSearchDataSource.m in Sources */,
B1B558CE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.m in Sources */,
@@ -4564,6 +4635,7 @@
B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */,
32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */,
B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */,
B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */,
B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */,
B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */,
B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */,
+4
View File
@@ -38,6 +38,10 @@ extern NSString *const kAppDelegateDidTapStatusBarNotification;
*/
extern NSString *const kAppDelegateNetworkStatusDidChangeNotification;
extern NSString *const AppDelegateDidValidateEmailNotification;
extern NSString *const AppDelegateDidValidateEmailNotificationSIDKey;
extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey;
@interface AppDelegate : UIResponder <UIApplicationDelegate, MXKCallViewControllerDelegate, UISplitViewControllerDelegate, UINavigationControllerDelegate, JitsiViewControllerDelegate, UNUserNotificationCenterDelegate>
{
BOOL isPushRegistered;
+17 -2
View File
@@ -84,6 +84,10 @@
NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapStatusBarNotification";
NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateNetworkStatusDidChangeNotification";
NSString *const AppDelegateDidValidateEmailNotification = @"AppDelegateDidValidateEmailNotification";
NSString *const AppDelegateDidValidateEmailNotificationSIDKey = @"AppDelegateDidValidateEmailNotificationSIDKey";
NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDelegateDidValidateEmailNotificationClientSecretKey";
@interface AppDelegate () <PKPushRegistryDelegate, GDPRConsentViewControllerDelegate, DeviceVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate>
{
/**
@@ -2136,7 +2140,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
NSMutableDictionary *queryParams;
[self parseUniversalLinkFragment:webURL.absoluteString outPathParams:&pathParams outQueryParams:&queryParams];
[identityService submit3PIDValidationToken:queryParams[@"token"] medium:kMX3PIDMediumEmail clientSecret:queryParams[@"client_secret"] sid:queryParams[@"sid"] success:^{
NSString *clientSecret = queryParams[@"client_secret"];
NSString *sid = queryParams[@"sid"];
[identityService submit3PIDValidationToken:queryParams[@"token"] medium:kMX3PIDMediumEmail clientSecret:clientSecret sid:sid success:^{
NSLog(@"[AppDelegate] handleUniversalLink. Email successfully validated.");
@@ -2150,7 +2157,15 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
else
{
// No nextLink in Vector world means validation for binding a new email
NSLog(@"[AppDelegate] handleUniversalLink. TODO: Complete email binding");
// Post a notification about email validation to make a chance to SettingsDiscoveryThreePidDetailsViewModel to make it discoverable or not by the identity server.
if (clientSecret && sid)
{
NSDictionary *userInfo = @{ AppDelegateDidValidateEmailNotificationClientSecretKey : clientSecret,
AppDelegateDidValidateEmailNotificationSIDKey : sid };
[[NSNotificationCenter defaultCenter] postNotificationName:AppDelegateDidValidateEmailNotification object:nil userInfo:userInfo];
}
}
} failure:^(NSError *error) {
+21
View File
@@ -360,6 +360,7 @@
"settings_user_settings" = "USER SETTINGS";
"settings_notifications_settings" = "NOTIFICATION SETTINGS";
"settings_calls_settings" = "CALLS";
"settings_discovery_settings" = "DISCOVERY";
"settings_user_interface" = "USER INTERFACE";
"settings_ignored_users" = "IGNORED USERS";
"settings_contacts" = "LOCAL CONTACTS";
@@ -390,6 +391,9 @@
"settings_change_password" = "Change password";
"settings_night_mode" = "Night Mode";
"settings_fail_to_update_profile" = "Fail to update profile";
"settings_three_pids_management_information_part1" = "Manage which email addresses or phone numbers you can use to log in or recover your account here. Control who can find you in ";
"settings_three_pids_management_information_part2" = "Discovery";
"settings_three_pids_management_information_part3" = ".";
"settings_enable_push_notif" = "Notifications on this device";
"settings_show_decrypted_content" = "Show decrypted content";
@@ -486,6 +490,23 @@
"settings_devices_description" = "A device's public name is visible to people you communicate with";
"settings_discovery_no_identity_server" = "You are not currently using an identity server. To be discoverable by existing contacts you known, add one.";
"settings_discovery_terms_not_signed" = "Agree to the Identity Server (%@) Terms of Service to allow yourself to be discoverable by email address or phone number.";
"settings_discovery_three_pids_management_information_part1" = "Manage which email addresses or phone numbers other users can use to discover you and use to invite you to rooms. Add or remove email addresses or phone numbers from this list in ";
"settings_discovery_three_pids_management_information_part2" = "User Settings";
"settings_discovery_three_pids_management_information_part3" = ".";
"settings_discovery_error_message" = "An error occured. Please retry.";
"settings_discovery_three_pid_details_title_email" = "Manage email";
"settings_discovery_three_pid_details_information_email" = "Manage preferences for this email address, which other users can use to discover you and use to invite you to rooms. Add or remove email addresses in Accounts.";
"settings_discovery_three_pid_details_title_phone_number" = "Manage phone number";
"settings_discovery_three_pid_details_information_phone_number" = "Manage preferences for this phone number, which other users can use to discover you and use to invite you to rooms. Add or remove phone numbers in Accounts.";
"settings_discovery_three_pid_details_share_action" = "Share";
"settings_discovery_three_pid_details_revoke_action" = "Revoke";
"settings_discovery_three_pid_details_cancel_email_validation_action" = "Cancel email validation";
"settings_discovery_three_pid_details_enter_sms_code_action" = "Enter SMS activation code";
// Room Details
"room_details_title" = "Room Details";
"room_details_people" = "Members";
+5
View File
@@ -97,6 +97,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.ServiceTermsModalScreenViewController>(storyboard: ServiceTermsModalScreenViewController.self)
}
internal enum SettingsDiscoveryThreePidDetailsViewController: StoryboardType {
internal static let storyboardName = "SettingsDiscoveryThreePidDetailsViewController"
internal static let initialScene = InitialSceneType<Riot.SettingsDiscoveryThreePidDetailsViewController>(storyboard: SettingsDiscoveryThreePidDetailsViewController.self)
}
internal enum SimpleScreenTemplateViewController: StoryboardType {
internal static let storyboardName = "SimpleScreenTemplateViewController"
+72
View File
@@ -2514,6 +2514,66 @@ internal enum VectorL10n {
internal static var settingsDevicesDescription: String {
return VectorL10n.tr("Vector", "settings_devices_description")
}
/// An error occured. Please retry.
internal static var settingsDiscoveryErrorMessage: String {
return VectorL10n.tr("Vector", "settings_discovery_error_message")
}
/// You are not currently using an identity server. To be discoverable by existing contacts you known, add one.
internal static var settingsDiscoveryNoIdentityServer: String {
return VectorL10n.tr("Vector", "settings_discovery_no_identity_server")
}
/// DISCOVERY
internal static var settingsDiscoverySettings: String {
return VectorL10n.tr("Vector", "settings_discovery_settings")
}
/// Agree to the Identity Server (%@) Terms of Service to allow yourself to be discoverable by email address or phone number.
internal static func settingsDiscoveryTermsNotSigned(_ p1: String) -> String {
return VectorL10n.tr("Vector", "settings_discovery_terms_not_signed", p1)
}
/// Cancel email validation
internal static var settingsDiscoveryThreePidDetailsCancelEmailValidationAction: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_cancel_email_validation_action")
}
/// Enter SMS activation code
internal static var settingsDiscoveryThreePidDetailsEnterSmsCodeAction: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_enter_sms_code_action")
}
/// Manage preferences for this email address, which other users can use to discover you and use to invite you to rooms. Add or remove email addresses in Accounts.
internal static var settingsDiscoveryThreePidDetailsInformationEmail: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_information_email")
}
/// Manage preferences for this phone number, which other users can use to discover you and use to invite you to rooms. Add or remove phone numbers in Accounts.
internal static var settingsDiscoveryThreePidDetailsInformationPhoneNumber: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_information_phone_number")
}
/// Revoke
internal static var settingsDiscoveryThreePidDetailsRevokeAction: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_revoke_action")
}
/// Share
internal static var settingsDiscoveryThreePidDetailsShareAction: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_share_action")
}
/// Manage email
internal static var settingsDiscoveryThreePidDetailsTitleEmail: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_title_email")
}
/// Manage phone number
internal static var settingsDiscoveryThreePidDetailsTitlePhoneNumber: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_title_phone_number")
}
/// Manage which email addresses or phone numbers other users can use to discover you and use to invite you to rooms. Add or remove email addresses or phone numbers from this list in
internal static var settingsDiscoveryThreePidsManagementInformationPart1: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pids_management_information_part1")
}
/// User Settings
internal static var settingsDiscoveryThreePidsManagementInformationPart2: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pids_management_information_part2")
}
/// .
internal static var settingsDiscoveryThreePidsManagementInformationPart3: String {
return VectorL10n.tr("Vector", "settings_discovery_three_pids_management_information_part3")
}
/// Display Name
internal static var settingsDisplayName: String {
return VectorL10n.tr("Vector", "settings_display_name")
@@ -2794,6 +2854,18 @@ internal enum VectorL10n {
internal static var settingsThirdPartyNotices: String {
return VectorL10n.tr("Vector", "settings_third_party_notices")
}
/// Manage which email addresses or phone numbers you can use to log in or recover your account here. Control who can find you in
internal static var settingsThreePidsManagementInformationPart1: String {
return VectorL10n.tr("Vector", "settings_three_pids_management_information_part1")
}
/// Discovery
internal static var settingsThreePidsManagementInformationPart2: String {
return VectorL10n.tr("Vector", "settings_three_pids_management_information_part2")
}
/// .
internal static var settingsThreePidsManagementInformationPart3: String {
return VectorL10n.tr("Vector", "settings_three_pids_management_information_part3")
}
/// Settings
internal static var settingsTitle: String {
return VectorL10n.tr("Vector", "settings_title")
@@ -34,6 +34,10 @@ final class ActivityIndicatorPresenter: ActivityIndicatorPresenterType {
private weak var activityIndicatorView: ActivityIndicatorView?
private weak var presentingView: UIView?
var isPresenting: Bool {
return self.activityIndicatorView != nil
}
// MARK: - Public
func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)? = nil) {
@@ -69,6 +69,10 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
viewController.present(serviceTermsModalCoordinator.toPresentable(), animated: animated, completion: nil)
serviceTermsModalCoordinator.start()
if let coordinator = self.coordinator {
coordinator.toPresentable().dismiss(animated: false, completion: nil)
}
self.coordinator = serviceTermsModalCoordinator
}
@@ -0,0 +1,245 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
@objc protocol SettingsDiscoveryTableViewSectionDelegate: class {
func settingsDiscoveryTableViewSection(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection, tableViewCellClass: MXKTableViewCell.Type, forRow: Int) -> MXKTableViewCell
func settingsDiscoveryTableViewSectionDidUpdate(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection)
}
private enum DiscoverySectionRows {
case info(text: String)
case attributedInfo(attributedText: NSAttributedString)
case button(title: String, action: () -> Void)
case threePid(threePid: MX3PID)
}
@objc final class SettingsDiscoveryTableViewSection: NSObject, Themable {
// MARK: - Constants
private enum Constants {
static let defaultFont = UIFont.systemFont(ofSize: 17.0)
}
// MARK: - Properties
@objc weak var delegate: SettingsDiscoveryTableViewSectionDelegate?
// MARK: Private
private var theme: Theme!
private var viewModel: SettingsDiscoveryViewModel
// Need to know the state to make `cellForRow` deliver cells accordingly
private var viewState: SettingsDiscoveryViewState = .loading {
didSet {
self.updateRows()
}
}
private var discoveryRows: [DiscoverySectionRows] = []
// MARK: - Setup
@objc init(viewModel: SettingsDiscoveryViewModel) {
self.theme = ThemeService.shared().theme
self.viewModel = viewModel
super.init()
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .load)
self.registerThemeServiceDidChangeThemeNotification()
}
// MARK: - Public
@objc func numberOfRows() -> Int {
return self.discoveryRows.count
}
@objc func cellForRow(atRow row: Int) -> UITableViewCell {
let discoveryRow = self.discoveryRows[row]
var cell: UITableViewCell?
let enableInteraction: Bool
if case .loading = self.viewState {
enableInteraction = false
} else {
enableInteraction = true
}
switch discoveryRow {
case .info(let infoText):
if let infoCell: MXKTableViewCell = self.cellType(at: row) {
infoCell.textLabel?.numberOfLines = 0
infoCell.textLabel?.text = infoText
infoCell.selectionStyle = .none
cell = infoCell
}
case .attributedInfo(attributedText: let infoText):
if let infoCell: MXKTableViewCell = self.cellType(at: row) {
infoCell.textLabel?.numberOfLines = 0
infoCell.textLabel?.attributedText = infoText
infoCell.selectionStyle = .none
cell = infoCell
}
case .button(title: let title, action: let action):
if let buttonCell: MXKTableViewCellWithButton = self.cellType(at: row) {
buttonCell.mxkButton.setTitle(title, for: .normal)
buttonCell.mxkButton.setTitle(title, for: .highlighted)
buttonCell.mxkButton.vc_addAction(action: action)
buttonCell.mxkButton.isEnabled = enableInteraction
cell = buttonCell
}
case .threePid(let threePid):
if let detailCell: MXKTableViewCell = self.cellType(at: row) {
detailCell.accessoryType = .disclosureIndicator
detailCell.textLabel?.text = threePid.address
detailCell.isUserInteractionEnabled = enableInteraction
cell = detailCell
}
}
return cell ?? UITableViewCell()
}
@objc func reload() {
self.viewModel.process(viewAction: .load)
}
@objc func selectRow(_ row: Int) {
let discoveryRow = self.discoveryRows[row]
switch discoveryRow {
case .threePid(threePid: let threePid):
self.viewModel.process(viewAction: .select(threePid: threePid))
case .attributedInfo(attributedText: _):
if case let .loaded(displayMode) = self.viewState {
switch displayMode {
case .noThreePidsAdded, .threePidsAdded:
self.viewModel.process(viewAction: .tapUserSettingsLink)
default:
break
}
}
default:
break
}
}
func update(theme: Theme) {
self.theme = theme
self.updateRows()
}
// MARK: - Private
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func cellType<T: MXKTableViewCell>(at row: Int) -> T? {
let klass: T.Type = T.self
let tableViewCell = delegate?.settingsDiscoveryTableViewSection(self, tableViewCellClass: klass, forRow: row)
return tableViewCell as? T
}
private func updateRows() {
let discoveryRows: [DiscoverySectionRows]
switch self.viewState {
case .loading:
discoveryRows = self.discoveryRows
case .loaded(let displayMode):
switch displayMode {
case .noIdentityServer:
discoveryRows = [
.info(text: VectorL10n.settingsDiscoveryNoIdentityServer)
]
case .termsNotSigned(let host):
discoveryRows = [
.info(text: VectorL10n.settingsDiscoveryTermsNotSigned(host)),
.button(title: VectorL10n.accept, action: { [weak self] in
self?.viewModel.process(viewAction: .acceptTerms)
})
]
case .noThreePidsAdded:
discoveryRows = [
.attributedInfo(attributedText: self.threePidsManagementInfoAttributedString())
]
case .threePidsAdded(let emails, let phoneNumbers):
let emailThreePids = emails.map { (email) -> DiscoverySectionRows in
return .threePid(threePid: email)
}
let phoneNumbersThreePids = phoneNumbers.map { (phoneNumber) -> DiscoverySectionRows in
return .threePid(threePid: phoneNumber)
}
var threePidsRows: [DiscoverySectionRows] = []
threePidsRows.append(contentsOf: emailThreePids)
threePidsRows.append(contentsOf: phoneNumbersThreePids)
threePidsRows.append(.attributedInfo(attributedText: self.threePidsManagementInfoAttributedString()))
discoveryRows = threePidsRows
}
case .error:
discoveryRows = [
.info(text: VectorL10n.settingsDiscoveryErrorMessage),
.button(title: VectorL10n.retry, action: { [weak self] in
self?.viewModel.process(viewAction: .load)
})
]
}
self.discoveryRows = discoveryRows
}
private func threePidsManagementInfoAttributedString() -> NSAttributedString {
let attributedInfoString = NSMutableAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart1,
attributes: [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.defaultFont])
attributedInfoString.append(NSAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart2,
attributes: [.foregroundColor: self.theme.tintColor, .font: Constants.defaultFont]))
attributedInfoString.append(NSAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart3,
attributes: [.foregroundColor: self.theme.tintColor, .font: Constants.defaultFont]))
return attributedInfoString
}
}
// MARK: - SettingsDiscoveryViewModelViewDelegate
extension SettingsDiscoveryTableViewSection: SettingsDiscoveryViewModelViewDelegate {
func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModelType, didUpdateViewState viewState: SettingsDiscoveryViewState) {
self.viewState = viewState
// The tableview datasource will call `self.cellForRow()`
self.delegate?.settingsDiscoveryTableViewSectionDidUpdate(self)
}
}
@@ -0,0 +1,25 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// SettingsDiscoveryTableViewSection view actions exposed to view model
enum SettingsDiscoveryViewAction {
case load
case acceptTerms
case select(threePid: MX3PID)
case tapUserSettingsLink
}
@@ -0,0 +1,200 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
@objc final class SettingsDiscoveryViewModel: NSObject, SettingsDiscoveryViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var identityService: MXIdentityService?
private var serviceTerms: MXServiceTerms?
private var viewState: SettingsDiscoveryViewState?
private var threePIDs: [MX3PID] = []
// MARK: Public
weak var viewDelegate: SettingsDiscoveryViewModelViewDelegate?
@objc weak var coordinatorDelegate: SettingsDiscoveryViewModelCoordinatorDelegate?
// MARK: - Setup
@objc init(session: MXSession, thirdPartyIdentifiers: [MXThirdPartyIdentifier]) {
self.session = session
let identityService = session.identityService
if let identityService = identityService {
self.serviceTerms = MXServiceTerms(baseUrl: identityService.identityServer, serviceType: MXServiceTypeIdentityService, matrixSession: session, accessToken: nil)
}
self.identityService = identityService
self.threePIDs = SettingsDiscoveryViewModel.threePids(from: thirdPartyIdentifiers)
super.init()
}
// MARK: - Public
func process(viewAction: SettingsDiscoveryViewAction) {
switch viewAction {
case .load:
self.checkTerms()
case .acceptTerms:
self.acceptTerms()
case .select(threePid: let threePid):
self.coordinatorDelegate?.settingsDiscoveryViewModel(self, didSelectThreePidWith: threePid.medium.identifier, and: threePid.address)
case .tapUserSettingsLink:
self.coordinatorDelegate?.settingsDiscoveryViewModelDidTapUserSettingsLink(self)
}
}
@objc func update(thirdPartyIdentifiers: [MXThirdPartyIdentifier]) {
self.threePIDs = SettingsDiscoveryViewModel.threePids(from: thirdPartyIdentifiers)
// Update view state only if three3pids was previously
guard let viewState = self.viewState,
case let .loaded(displayMode: displayMode) = viewState else {
return
}
let canDisplayThreePids: Bool
switch displayMode {
case .threePidsAdded, .noThreePidsAdded:
canDisplayThreePids = true
default:
canDisplayThreePids = false
}
if canDisplayThreePids {
self.updateViewStateFromCurrentThreePids()
}
}
// MARK: - Private
private func checkTerms() {
guard let identityService = self.identityService, let serviceTerms = self.serviceTerms else {
self.update(viewState: .loaded(displayMode: .noIdentityServer))
return
}
guard self.canCheckTerms() else {
return
}
self.update(viewState: .loading)
serviceTerms.areAllTermsAgreed({ (areAllTermsAgreed) in
if areAllTermsAgreed {
// Display three pids if presents
self.updateViewStateFromCurrentThreePids()
} else {
let identityServer = identityService.identityServer
let identityServerHost = URL(string: identityServer)?.host ?? identityServer
self.update(viewState: .loaded(displayMode: .termsNotSigned(host: identityServerHost)))
}
}, failure: { (error) in
self.update(viewState: .error(error))
})
}
private func acceptTerms() {
guard let identityService = self.identityService else {
self.update(viewState: .loaded(displayMode: .noIdentityServer))
return
}
// Launch an identity server request to trigger terms modal apparition
identityService.account { (response) in
switch response {
case .success:
// Display three pids if presents
self.updateViewStateFromCurrentThreePids()
case .failure(let error):
if MXError(nsError: error)?.errcode == kMXErrCodeStringTermsNotSigned {
// Identity terms modal should appear
} else {
self.update(viewState: .error(error))
}
}
}
}
private func canCheckTerms() -> Bool {
guard let viewState = self.viewState else {
return true
}
let canCheckTerms: Bool
if case .loading = viewState {
canCheckTerms = false
} else {
canCheckTerms = true
}
return canCheckTerms
}
private func updateViewStateFromCurrentThreePids() {
self.updateViewState(with: self.threePIDs)
}
private func updateViewState(with threePids: [MX3PID]) {
let viewState: SettingsDiscoveryViewState
if threePids.isEmpty {
viewState = .loaded(displayMode: .noThreePidsAdded)
} else {
let emails = threePids.compactMap { (threePid) -> MX3PID? in
if case .email = threePid.medium {
return threePid
} else {
return nil
}
}
let phoneNumbers = threePids.compactMap { (threePid) -> MX3PID? in
if case .msisdn = threePid.medium {
return threePid
} else {
return nil
}
}
viewState = .loaded(displayMode: .threePidsAdded(emails: emails, phoneNumbers: phoneNumbers))
}
self.update(viewState: viewState)
}
private func update(viewState: SettingsDiscoveryViewState) {
self.viewState = viewState
self.viewDelegate?.settingsDiscoveryViewModel(self, didUpdateViewState: viewState)
}
private class func threePids(from thirdPartyIdentifiers: [MXThirdPartyIdentifier]) -> [MX3PID] {
return thirdPartyIdentifiers.map({ (thirdPartyIdentifier) -> MX3PID in
return MX3PID(medium: MX3PID.Medium(identifier: thirdPartyIdentifier.medium), address: thirdPartyIdentifier.address)
})
}
}
@@ -0,0 +1,35 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol SettingsDiscoveryViewModelViewDelegate: class {
func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModelType, didUpdateViewState viewState: SettingsDiscoveryViewState)
}
@objc protocol SettingsDiscoveryViewModelCoordinatorDelegate: class {
func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModel, didSelectThreePidWith medium: String, and address: String)
func settingsDiscoveryViewModelDidTapUserSettingsLink(_ viewModel: SettingsDiscoveryViewModel)
}
protocol SettingsDiscoveryViewModelType {
var viewDelegate: SettingsDiscoveryViewModelViewDelegate? { get set }
var coordinatorDelegate: SettingsDiscoveryViewModelCoordinatorDelegate? { get set }
func process(viewAction: SettingsDiscoveryViewAction)
}
@@ -0,0 +1,37 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// SettingsDiscoveryTableViewSection view state
enum SettingsDiscoveryViewState {
case loading
case loaded(displayMode: SettingsDiscoveryDisplayMode)
case error(Error)
}
/// SettingsDiscoveryTableViewSection `loaded` view state dipslay modes
///
/// - noIdentityServer: No identity server configured.
/// - termsNotSigned: Identity server terms are not signed.
/// - noThreePidsAdded: No three pids added to the user HS account.
/// - threePidsAdded: Three pids added to the user HS account.
enum SettingsDiscoveryDisplayMode {
case noIdentityServer
case termsNotSigned(host: String)
case noThreePidsAdded
case threePidsAdded(emails: [MX3PID], phoneNumbers: [MX3PID])
}
@@ -0,0 +1,56 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import UIKit
final class SettingsDiscoveryThreePidDetailsCoordinator: SettingsDiscoveryThreePidDetailsCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var settingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePidDetailsViewModelType
private let settingsDiscoveryThreePidDetailsViewController: SettingsDiscoveryThreePidDetailsViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
// MARK: - Setup
init(session: MXSession, threePid: MX3PID) {
self.session = session
let settingsDiscoveryThreePidDetailsViewModel = SettingsDiscoveryThreePidDetailsViewModel(session: self.session, threePid: threePid)
let settingsDiscoveryThreePidDetailsViewController = SettingsDiscoveryThreePidDetailsViewController.instantiate(with: settingsDiscoveryThreePidDetailsViewModel)
self.settingsDiscoveryThreePidDetailsViewModel = settingsDiscoveryThreePidDetailsViewModel
self.settingsDiscoveryThreePidDetailsViewController = settingsDiscoveryThreePidDetailsViewController
}
// MARK: - Public methods
func start() {
}
func toPresentable() -> UIViewController {
return self.settingsDiscoveryThreePidDetailsViewController
}
}
@@ -0,0 +1,63 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter enables to start SettingsDiscoveryThreePidDetailsCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
@objcMembers
final class SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let threePid: MX3PID
private var coordinator: SettingsDiscoveryThreePidDetailsCoordinator?
private var router: NavigationRouter?
// MARK: - Setup
init(session: MXSession, medium: String, adress: String) {
self.session = session
self.threePid = MX3PID(medium: MX3PID.Medium(identifier: medium), address: adress)
super.init()
}
// MARK: - Public
func push(from navigationController: UINavigationController, animated: Bool, popCompletion: (() -> Void)?) {
let router = NavigationRouter(navigationController: navigationController)
let settingsDiscoveryThreePidDetailsCoordinator = SettingsDiscoveryThreePidDetailsCoordinator(session: self.session, threePid: self.threePid)
router.push(settingsDiscoveryThreePidDetailsCoordinator, animated: animated) { [weak self] in
self?.coordinator = nil
self?.router = nil
popCompletion?()
}
settingsDiscoveryThreePidDetailsCoordinator.start()
self.coordinator = settingsDiscoveryThreePidDetailsCoordinator
self.router = router
}
}
@@ -0,0 +1,23 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// `SettingsDiscoveryThreePidDetailsCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol SettingsDiscoveryThreePidDetailsCoordinatorType: Coordinator, Presentable {
}
@@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// SettingsDiscoveryThreePidDetailsViewController view actions exposed to view model
enum SettingsDiscoveryThreePidDetailsViewAction {
case load
case share
case revoke
case cancelEmailValidation
case confirmEmailValidation
case enterSMSCode(_ code: String)
}
@@ -0,0 +1,152 @@
<?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="ezK-Hg-0xv">
<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>
<!--Settings Discovery Three Pid Details View Controller-->
<scene sceneID="3dE-H2-gcP">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="ezK-Hg-0xv" customClass="SettingsDiscoveryThreePidDetailsViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="R2r-2G-yM5">
<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="q1k-fN-38j">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aP2-lr-60e">
<rect key="frame" x="0.0" y="0.0" width="375" height="277"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1h7-ek-3CO">
<rect key="frame" x="0.0" y="0.0" width="375" height="277"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="H6I-jF-SbB">
<rect key="frame" x="0.0" y="40" width="375" height="50"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" text="email" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vPl-rf-DWf">
<rect key="frame" x="20" y="16" width="36.5" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="foo@matrix.org" textAlignment="right" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SP5-xI-3zJ">
<rect key="frame" x="66.5" y="16" width="288.5" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="SP5-xI-3zJ" firstAttribute="top" secondItem="vPl-rf-DWf" secondAttribute="top" id="5Mc-1w-3Qy"/>
<constraint firstAttribute="trailing" secondItem="SP5-xI-3zJ" secondAttribute="trailing" constant="20" id="9CG-58-qE1"/>
<constraint firstItem="vPl-rf-DWf" firstAttribute="leading" secondItem="H6I-jF-SbB" secondAttribute="leading" constant="20" id="Ngy-Un-QjA"/>
<constraint firstItem="vPl-rf-DWf" firstAttribute="top" secondItem="H6I-jF-SbB" secondAttribute="top" constant="16" id="eoq-0N-Ka3"/>
<constraint firstItem="SP5-xI-3zJ" firstAttribute="leading" secondItem="vPl-rf-DWf" secondAttribute="trailing" constant="10" id="gWa-Bh-BGZ"/>
<constraint firstAttribute="bottom" secondItem="vPl-rf-DWf" secondAttribute="bottom" constant="16" id="kmG-LA-aJL"/>
<constraint firstItem="SP5-xI-3zJ" firstAttribute="bottom" secondItem="vPl-rf-DWf" secondAttribute="bottom" id="tt6-jQ-Gtv"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PNH-63-Z3c">
<rect key="frame" x="0.0" y="115" width="375" height="50"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GnW-2q-pSU">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="8qU-Gg-4dL"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="Share">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="operationButtonAction:" destination="ezK-Hg-0xv" eventType="touchUpInside" id="uLq-yW-mh2"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="GnW-2q-pSU" firstAttribute="leading" secondItem="PNH-63-Z3c" secondAttribute="leading" id="45P-d7-TNc"/>
<constraint firstItem="GnW-2q-pSU" firstAttribute="top" secondItem="PNH-63-Z3c" secondAttribute="top" id="6w9-y9-Cic"/>
<constraint firstAttribute="bottom" secondItem="GnW-2q-pSU" secondAttribute="bottom" id="K0b-tM-nlb"/>
<constraint firstAttribute="trailing" secondItem="GnW-2q-pSU" secondAttribute="trailing" id="fvC-FK-JJE"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fYa-31-Xfd">
<rect key="frame" x="20" y="185" width="335" height="72"/>
<string key="text">Manage preferences for this email address, which other users can use to discover you and use to invite you to rooms. Add or remove email addresses in Accounts.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="H6I-jF-SbB" secondAttribute="trailing" id="2eD-FF-4pI"/>
<constraint firstItem="fYa-31-Xfd" firstAttribute="top" secondItem="PNH-63-Z3c" secondAttribute="bottom" constant="20" id="7m2-AA-5BM"/>
<constraint firstItem="H6I-jF-SbB" firstAttribute="top" secondItem="1h7-ek-3CO" secondAttribute="top" constant="40" id="9nH-V1-qst"/>
<constraint firstItem="PNH-63-Z3c" firstAttribute="top" secondItem="H6I-jF-SbB" secondAttribute="bottom" constant="25" id="JJC-gO-Yl3"/>
<constraint firstAttribute="bottom" secondItem="fYa-31-Xfd" secondAttribute="bottom" constant="20" id="LMW-Qk-Dv4"/>
<constraint firstItem="fYa-31-Xfd" firstAttribute="leading" secondItem="1h7-ek-3CO" secondAttribute="leading" constant="20" id="PlF-wq-3sl"/>
<constraint firstItem="PNH-63-Z3c" firstAttribute="leading" secondItem="1h7-ek-3CO" secondAttribute="leading" id="alO-Yb-wVp"/>
<constraint firstAttribute="trailing" secondItem="PNH-63-Z3c" secondAttribute="trailing" id="brL-L8-ch1"/>
<constraint firstAttribute="width" priority="750" constant="500" id="l3L-bE-s7u"/>
<constraint firstItem="H6I-jF-SbB" firstAttribute="leading" secondItem="1h7-ek-3CO" secondAttribute="leading" id="nAN-0L-p44"/>
<constraint firstAttribute="trailing" secondItem="fYa-31-Xfd" secondAttribute="trailing" constant="20" id="pTH-Uc-hX7"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="1h7-ek-3CO" firstAttribute="centerX" secondItem="aP2-lr-60e" secondAttribute="centerX" id="CV4-kX-dzd"/>
<constraint firstItem="1h7-ek-3CO" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="aP2-lr-60e" secondAttribute="leading" id="F1Q-cV-TEV"/>
<constraint firstAttribute="bottom" secondItem="1h7-ek-3CO" secondAttribute="bottom" id="Hyc-7c-jZe"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="1h7-ek-3CO" secondAttribute="trailing" id="MPu-qV-x2a"/>
<constraint firstItem="1h7-ek-3CO" firstAttribute="top" secondItem="aP2-lr-60e" secondAttribute="top" id="Ol3-Vk-aV2"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="aP2-lr-60e" firstAttribute="top" secondItem="q1k-fN-38j" secondAttribute="top" id="52d-aL-BvB"/>
<constraint firstAttribute="trailing" secondItem="aP2-lr-60e" secondAttribute="trailing" id="E3H-IS-rRu"/>
<constraint firstItem="aP2-lr-60e" firstAttribute="width" secondItem="q1k-fN-38j" secondAttribute="width" id="JlK-tw-JnA"/>
<constraint firstAttribute="bottom" secondItem="aP2-lr-60e" secondAttribute="bottom" id="dYG-fE-MEt"/>
<constraint firstItem="aP2-lr-60e" firstAttribute="leading" secondItem="q1k-fN-38j" secondAttribute="leading" id="whl-EE-953"/>
</constraints>
</scrollView>
</subviews>
<color key="backgroundColor" red="0.94509803920000002" green="0.96078431369999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="juo-3g-nXq" firstAttribute="top" secondItem="q1k-fN-38j" secondAttribute="top" id="9hw-K8-RPD"/>
<constraint firstAttribute="bottom" secondItem="q1k-fN-38j" secondAttribute="bottom" id="Fad-dA-ZfQ"/>
<constraint firstItem="juo-3g-nXq" firstAttribute="trailing" secondItem="q1k-fN-38j" secondAttribute="trailing" id="JhW-Hh-xAm"/>
<constraint firstItem="q1k-fN-38j" firstAttribute="leading" secondItem="juo-3g-nXq" secondAttribute="leading" id="iYf-jl-9Jd"/>
</constraints>
<viewLayoutGuide key="safeArea" id="juo-3g-nXq"/>
</view>
<connections>
<outlet property="informationLabel" destination="fYa-31-Xfd" id="pgq-O8-lEO"/>
<outlet property="operationButton" destination="GnW-2q-pSU" id="7iB-Gx-ppD"/>
<outlet property="scrollView" destination="q1k-fN-38j" id="hkr-ze-2Ht"/>
<outlet property="threePidAdressLabel" destination="SP5-xI-3zJ" id="q5X-AN-eed"/>
<outlet property="threePidBackgroundView" destination="H6I-jF-SbB" id="s57-Tg-UUJ"/>
<outlet property="threePidTitleLabel" destination="vPl-rf-DWf" id="SMX-J1-71T"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Pf0-Pr-ZvT" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3772" y="-774"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,295 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class SettingsDiscoveryThreePidDetailsViewController: UIViewController {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var threePidBackgroundView: UIView!
@IBOutlet private weak var threePidTitleLabel: UILabel!
@IBOutlet private weak var threePidAdressLabel: UILabel!
@IBOutlet private weak var operationButton: UIButton!
@IBOutlet private weak var informationLabel: UILabel!
// MARK: Private
private var viewModel: SettingsDiscoveryThreePidDetailsViewModelType!
private var theme: Theme!
private var keyboardAvoider: KeyboardAvoider?
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private weak var presentedAlertController: UIAlertController?
private var displayMode: SettingsDiscoveryThreePidDetailsDisplayMode?
// MARK: - Setup
class func instantiate(with viewModel: SettingsDiscoveryThreePidDetailsViewModelType) -> SettingsDiscoveryThreePidDetailsViewController {
let viewController = StoryboardScene.SettingsDiscoveryThreePidDetailsViewController.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.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.scrollView)
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .load)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAvoider?.startAvoiding()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.keyboardAvoider?.stopAvoiding()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.threePidBackgroundView.backgroundColor = theme.backgroundColor
self.threePidTitleLabel.textColor = theme.textPrimaryColor
self.threePidAdressLabel.textColor = theme.textSecondaryColor
self.informationLabel.textColor = theme.textSecondaryColor
self.operationButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.operationButton)
}
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.scrollView.keyboardDismissMode = .interactive
self.render(threePid: self.viewModel.threePid)
}
private func render(threePid: MX3PID) {
let title: String
let threePidTitle: String
let informationText: String
switch threePid.medium {
case .email:
title = VectorL10n.settingsDiscoveryThreePidDetailsTitleEmail
threePidTitle = VectorL10n.settingsEmailAddress
informationText = VectorL10n.settingsDiscoveryThreePidDetailsInformationEmail
case .msisdn:
title = VectorL10n.settingsDiscoveryThreePidDetailsTitlePhoneNumber
threePidTitle = VectorL10n.settingsPhoneNumber
informationText = VectorL10n.settingsDiscoveryThreePidDetailsInformationPhoneNumber
default:
title = ""
threePidTitle = ""
informationText = ""
}
self.title = title
self.threePidTitleLabel.text = threePidTitle
self.threePidAdressLabel.text = threePid.address
self.informationLabel.text = informationText
}
private func render(viewState: SettingsDiscoveryThreePidDetailsViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(displayMode: let displayMode):
self.renderLoaded(displayMode: displayMode)
case .error(let error):
self.render(error: error)
}
}
private func renderLoaded(displayMode: SettingsDiscoveryThreePidDetailsDisplayMode) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
let operationButtonTitle: String?
let operationButtonColor: UIColor?
let operationButtonEnabled: Bool
self.presentedAlertController?.dismiss(animated: false, completion: nil)
switch displayMode {
case .share:
operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsShareAction
operationButtonColor = self.theme.tintColor
operationButtonEnabled = true
case .revoke:
operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsRevokeAction
operationButtonColor = self.theme.warningColor
operationButtonEnabled = true
case .pendingEmailVerification:
self.presentPendingEmailVerificationAlert()
operationButtonTitle = nil
operationButtonColor = nil
operationButtonEnabled = false
case .enterSMSCode:
operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsEnterSmsCodeAction
operationButtonColor = self.theme.tintColor
operationButtonEnabled = true
}
if let operationButtonTitle = operationButtonTitle {
self.operationButton.setTitle(operationButtonTitle, for: .normal)
}
if let operationButtonColor = operationButtonColor {
self.operationButton.setTitleColor(operationButtonColor, for: .normal)
}
self.operationButton.isEnabled = operationButtonEnabled
self.displayMode = displayMode
}
private func renderLoading() {
if self.activityPresenter.isPresenting == false {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
self.operationButton.isEnabled = false
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
self.operationButton.isEnabled = true
}
private func presentPendingEmailVerificationAlert() {
let alert = UIAlertController(title: Bundle.mxk_localizedString(forKey: "account_email_validation_title"),
message: Bundle.mxk_localizedString(forKey: "account_email_validation_message"),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: VectorL10n.continue, style: .default, handler: { _ in
self.viewModel.process(viewAction: .confirmEmailValidation)
}))
alert.addAction(UIAlertAction(title: Bundle.mxk_localizedString(forKey: "abort"), style: .cancel, handler: { _ in
self.viewModel.process(viewAction: .cancelEmailValidation)
}))
self.present(alert, animated: true, completion: nil)
self.presentedAlertController = alert
}
private func presentSMSActivationCodeAlert() {
let alert = UIAlertController(title: VectorL10n.authMsisdnValidationTitle,
message: VectorL10n.authMsisdnValidationMessage,
preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = nil
textField.keyboardType = .phonePad
}
alert.addAction(UIAlertAction(title: VectorL10n.authSubmit, style: .default, handler: { _ in
guard let textField = alert.textFields?.first, let smsCode = textField.text, smsCode.isEmpty == false else {
return
}
self.viewModel.process(viewAction: .enterSMSCode(smsCode))
}))
alert.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
self.presentedAlertController = alert
}
// MARK: - Actions
@IBAction private func operationButtonAction(_ sender: Any) {
guard let displayMode = self.displayMode else {
return
}
let viewAction: SettingsDiscoveryThreePidDetailsViewAction?
switch displayMode {
case .share:
viewAction = .share
case .revoke:
viewAction = .revoke
case .enterSMSCode:
viewAction = nil
self.presentSMSActivationCodeAlert()
default:
viewAction = nil
}
if let viewAction = viewAction {
self.viewModel.process(viewAction: viewAction)
}
}
}
// MARK: - SettingsDiscoveryThreePidDetailsViewModelViewDelegate
extension SettingsDiscoveryThreePidDetailsViewController: SettingsDiscoveryThreePidDetailsViewModelViewDelegate {
func settingsDiscoveryThreePidDetailsViewModel(_ viewModel: SettingsDiscoveryThreePidDetailsViewModelType, didUpdateViewState viewSate: SettingsDiscoveryThreePidDetailsViewState) {
self.render(viewState: viewSate)
}
}
@@ -0,0 +1,279 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum SettingsDiscoveryThreePidDetailsViewModelError: Error {
case unknown
}
private struct ThreePidRequestTokenInfo {
let clientSecret: String
let sid: String
let bind: Bool
}
final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePidDetailsViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var viewState: SettingsDiscoveryThreePidDetailsViewState?
private var currentThreePidRequestTokenInfo: ThreePidRequestTokenInfo?
// MARK: Public
let threePid: MX3PID
weak var viewDelegate: SettingsDiscoveryThreePidDetailsViewModelViewDelegate?
// MARK: - Setup
init(session: MXSession, threePid: MX3PID) {
self.session = session
self.threePid = threePid
}
// MARK: - Public
func process(viewAction: SettingsDiscoveryThreePidDetailsViewAction) {
switch viewAction {
case .load:
self.load()
case .share:
self.share()
case .revoke:
self.revoke()
case .cancelEmailValidation:
self.cancelEmailValidation()
case .confirmEmailValidation:
self.confirmEmailValidation()
case .enterSMSCode(let code):
self.validatePhoneNumber(with: code)
}
}
// MARK: - Private
private func load() {
self.update(viewState: .loading)
self.checkThreePidDiscoverability()
}
private func checkThreePidDiscoverability() {
self.isThreePidDiscoverable(self.threePid) { (response) in
switch response {
case .success(let isDiscoverable):
if isDiscoverable {
self.update(viewState: .loaded(displayMode: .revoke))
} else {
self.update(viewState: .loaded(displayMode: .share))
}
case .failure(let error):
self.update(viewState: .error(error))
}
}
}
private func share() {
self.requestToken(for: self.threePid, bind: true)
}
private func revoke() {
self.requestToken(for: self.threePid, bind: false)
}
private func requestToken(for threePid: MX3PID, bind: Bool, useOlderHomeserver: Bool = false) {
guard let restClient = self.session.matrixRestClient,
let clientSecret = MXTools.generateSecret() else {
return
}
let requestThreePidToken: (() -> Void) = {
switch self.threePid.medium {
case .email:
restClient.requestToken(forEmail: threePid.address, isDuringRegistration: false, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid) in
if let sid = sid {
self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind)
self.update(viewState: .loaded(displayMode: .pendingEmailVerification))
self.registerEmailValidationNotification()
} else {
self.update(viewState: .error(SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
}, failure: { error in
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDInUse, useOlderHomeserver == false {
self.requestToken(for: threePid, bind: bind, useOlderHomeserver: true)
} else {
self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
})
case .msisdn:
let formattedPhoneNumber = self.formattedPhoneNumber(from: threePid.address)
restClient.requestToken(forPhoneNumber: formattedPhoneNumber, isDuringRegistration: false, countryCode: nil, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid, msisdn) in
if let sid = sid {
self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind)
self.update(viewState: .loaded(displayMode: .enterSMSCode))
} else {
self.update(viewState: .error(SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
}, failure: { error in
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDInUse, useOlderHomeserver == false {
self.requestToken(for: threePid, bind: bind, useOlderHomeserver: true)
} else {
self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
})
default:
break
}
}
self.update(viewState: .loading)
if useOlderHomeserver {
restClient.remove3PID(address: threePid.address, medium: threePid.medium.identifier) { (response) in
switch response {
case .success:
requestThreePidToken()
case .failure(let error):
self.update(viewState: .error(error))
}
}
} else {
requestThreePidToken()
}
}
@discardableResult
private func isThreePidDiscoverable(_ threePid: MX3PID, completion: @escaping (_ response: MXResponse<Bool>) -> Void) -> MXHTTPOperation? {
guard let identityService = self.session.identityService else {
completion(.failure(SettingsDiscoveryThreePidDetailsViewModelError.unknown))
return nil
}
return identityService.lookup3PIDs([threePid]) { lookupResponse in
switch lookupResponse {
case .success(let threePids):
completion(.success(threePids.isEmpty == false))
case .failure(let error):
completion(.failure(error))
}
}
}
private func update(viewState: SettingsDiscoveryThreePidDetailsViewState) {
self.viewDelegate?.settingsDiscoveryThreePidDetailsViewModel(self, didUpdateViewState: viewState)
}
private func bindThreePid(_ threePid: MX3PID, threePidRequestTokenInfo: ThreePidRequestTokenInfo) {
guard let restClient = self.session.matrixRestClient else {
return
}
self.update(viewState: .loading)
restClient.addThirdPartyIdentifier(threePidRequestTokenInfo.sid, clientSecret: threePidRequestTokenInfo.clientSecret, bind: threePidRequestTokenInfo.bind) { response in
switch response {
case .success:
if case .email = threePid.medium {
self.unregisterEmailValidationNotification()
}
self.checkThreePidDiscoverability()
case .failure(let error):
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDAuthFailed {
self.update(viewState: .loaded(displayMode: .pendingEmailVerification))
} else {
if case .email = threePid.medium {
self.unregisterEmailValidationNotification()
}
self.update(viewState: .error(error))
}
}
}
}
// MARK: Email
private func cancelEmailValidation() {
self.unregisterEmailValidationNotification()
self.currentThreePidRequestTokenInfo = nil
self.checkThreePidDiscoverability()
}
private func confirmEmailValidation() {
guard let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo else {
return
}
self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo)
}
private func registerEmailValidationNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(handleEmailValidationNotification(notification:)), name: .AppDelegateDidValidateEmail, object: nil)
}
private func unregisterEmailValidationNotification() {
NotificationCenter.default.removeObserver(self, name: .AppDelegateDidValidateEmail, object: nil)
}
@objc private func handleEmailValidationNotification(notification: Notification) {
guard let userInfo = notification.userInfo,
let clientSecret = userInfo[AppDelegateDidValidateEmailNotificationClientSecretKey] as? String,
let sid = userInfo[AppDelegateDidValidateEmailNotificationSIDKey] as? String,
let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo,
threePidRequestTokenInfo.clientSecret == clientSecret,
threePidRequestTokenInfo.sid == sid else {
return
}
self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo)
}
// MARK: Phone number
private func formattedPhoneNumber(from phoneNumber: String) -> String {
guard phoneNumber.starts(with: "+") == false else {
return phoneNumber
}
return "+\(threePid.address)"
}
private func validatePhoneNumber(with activationCode: String) {
guard let identityService = self.session.identityService, let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo else {
return
}
identityService.submit3PIDValidationToken(activationCode, medium: MX3PID.Medium.msisdn.identifier, clientSecret: threePidRequestTokenInfo.clientSecret, sid: threePidRequestTokenInfo.sid) { (response) in
switch response {
case .success:
self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo)
case .failure(let error):
self.update(viewState: .error(error))
}
}
}
}
@@ -0,0 +1,33 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol SettingsDiscoveryThreePidDetailsViewModelViewDelegate: class {
func settingsDiscoveryThreePidDetailsViewModel(_ viewModel: SettingsDiscoveryThreePidDetailsViewModelType, didUpdateViewState viewSate: SettingsDiscoveryThreePidDetailsViewState)
}
/// Protocol describing the view model used by `SettingsDiscoveryThreePidDetailsViewController`
protocol SettingsDiscoveryThreePidDetailsViewModelType {
var threePid: MX3PID { get }
var viewDelegate: SettingsDiscoveryThreePidDetailsViewModelViewDelegate? { get set }
func process(viewAction: SettingsDiscoveryThreePidDetailsViewAction)
}
@@ -0,0 +1,33 @@
// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// SettingsDiscoveryThreePidDetailsViewController view state
enum SettingsDiscoveryThreePidDetailsViewState {
case loading
case loaded(displayMode: SettingsDiscoveryThreePidDetailsDisplayMode)
case error(Error)
}
enum SettingsDiscoveryThreePidDetailsDisplayMode {
case share
case revoke
case pendingEmailVerification
case enterSMSCode
}
+144 -3
View File
@@ -53,6 +53,7 @@ enum
SETTINGS_SECTION_USER_SETTINGS_INDEX,
SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX,
SETTINGS_SECTION_CALLS_INDEX,
SETTINGS_SECTION_DISCOVERY_INDEX,
SETTINGS_SECTION_USER_INTERFACE_INDEX,
SETTINGS_SECTION_IGNORED_USERS_INDEX,
SETTINGS_SECTION_CONTACTS_INDEX,
@@ -146,7 +147,8 @@ MXKEncryptionInfoViewDelegate,
KeyBackupSetupCoordinatorBridgePresenterDelegate,
KeyBackupRecoverCoordinatorBridgePresenterDelegate,
SignOutAlertPresenterDelegate,
SingleImagePickerPresenterDelegate>
SingleImagePickerPresenterDelegate,
SettingsDiscoveryTableViewSectionDelegate, SettingsDiscoveryViewModelCoordinatorDelegate>
{
// Current alert (if any).
UIAlertController *currentAlert;
@@ -193,6 +195,7 @@ SingleImagePickerPresenterDelegate>
NSInteger userSettingsPhoneStartIndex; // The user can have several linked phone numbers. Hence, the dynamic section items count
NSInteger userSettingsNewPhoneIndex; // This index also marks the end of the phone numbers list
NSInteger userSettingsChangePasswordIndex;
NSInteger userSettingsThreePidsInformation;
NSInteger userSettingsNightModeSepIndex;
NSInteger userSettingsNightModeIndex;
@@ -256,6 +259,10 @@ SingleImagePickerPresenterDelegate>
@property (nonatomic, weak) UIButton *signOutButton;
@property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter;
@property (nonatomic, strong) SettingsDiscoveryViewModel *settingsDiscoveryViewModel;
@property (nonatomic, strong) SettingsDiscoveryTableViewSection *settingsDiscoveryTableViewSection;
@property (nonatomic, strong) SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter;
@end
@implementation SettingsViewController
@@ -343,6 +350,8 @@ SingleImagePickerPresenterDelegate>
keyBackupSection.delegate = self;
}
}
[self setupDiscoverySection];
groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:self.mainSession];
[groupsDataSource finalizeInitialization];
@@ -490,6 +499,13 @@ SingleImagePickerPresenterDelegate>
newPhoneNumberCountryPicker = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.settingsDiscoveryTableViewSection reload];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
@@ -962,6 +978,9 @@ SingleImagePickerPresenterDelegate>
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
[account load3PIDs:^{
NSArray<MXThirdPartyIdentifier*> *thirdPartyIdentifiers = account.threePIDs ?: @[];
[self.settingsDiscoveryViewModel updateWithThirdPartyIdentifiers:thirdPartyIdentifiers];
// Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
// Note: The use of 'reloadData' handles the case where the account has been logged out.
[self refreshSettings];
@@ -1202,6 +1221,22 @@ SingleImagePickerPresenterDelegate>
}
}
- (void)setupDiscoverySection
{
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
NSArray<MXThirdPartyIdentifier*> *thirdPartyIdentifiers = account.threePIDs ?: @[];
SettingsDiscoveryViewModel *viewModel = [[SettingsDiscoveryViewModel alloc] initWithSession:self.mainSession thirdPartyIdentifiers:thirdPartyIdentifiers];
viewModel.coordinatorDelegate = self;
SettingsDiscoveryTableViewSection *discoverySection = [[SettingsDiscoveryTableViewSection alloc] initWithViewModel:viewModel];
discoverySection.delegate = self;
self.settingsDiscoveryViewModel = viewModel;
self.settingsDiscoveryTableViewSection = discoverySection;
}
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
@@ -1248,8 +1283,9 @@ SingleImagePickerPresenterDelegate>
userSettingsNewEmailIndex = userSettingsEmailStartIndex + account.linkedEmails.count;
userSettingsPhoneStartIndex = userSettingsNewEmailIndex + 1;
userSettingsNewPhoneIndex = userSettingsPhoneStartIndex + account.linkedPhoneNumbers.count;
count = userSettingsNewPhoneIndex + 1;
userSettingsThreePidsInformation = userSettingsNewPhoneIndex + 1;
count = userSettingsThreePidsInformation + 1;
}
else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX)
{
@@ -1264,6 +1300,10 @@ SingleImagePickerPresenterDelegate>
count -= 2;
}
}
else if (section == SETTINGS_SECTION_DISCOVERY_INDEX)
{
count = self.settingsDiscoveryTableViewSection.numberOfRows;
}
else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX)
{
count = USER_INTERFACE_COUNT;
@@ -1717,6 +1757,21 @@ SingleImagePickerPresenterDelegate>
cell = newPhoneCell;
}
}
else if (row == userSettingsThreePidsInformation)
{
MXKTableViewCell *threePidsInformationCell = [self getDefaultTableViewCell:self.tableView];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part1", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}];
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part2", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]];
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part3", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]];
threePidsInformationCell.textLabel.attributedText = attributedString;
threePidsInformationCell.textLabel.numberOfLines = 0;
threePidsInformationCell.selectionStyle = UITableViewCellSelectionStyleNone;
cell = threePidsInformationCell;
}
else if (row == userSettingsChangePasswordIndex)
{
MXKTableViewCellWithLabelAndTextField *passwordCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath];
@@ -1870,6 +1925,10 @@ SingleImagePickerPresenterDelegate>
cell = globalInfoCell;
}
}
else if (section == SETTINGS_SECTION_DISCOVERY_INDEX)
{
cell = [self.settingsDiscoveryTableViewSection cellForRowAtRow:row];
}
else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX)
{
if (row == USER_INTERFACE_LANGUAGE_INDEX)
@@ -2369,6 +2428,10 @@ SingleImagePickerPresenterDelegate>
{
return NSLocalizedStringFromTable(@"settings_calls_settings", @"Vector", nil);
}
else if (section == SETTINGS_SECTION_DISCOVERY_INDEX)
{
return NSLocalizedStringFromTable(@"settings_discovery_settings", @"Vector", nil);
}
else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX)
{
return NSLocalizedStringFromTable(@"settings_user_interface", @"Vector", nil);
@@ -2602,6 +2665,15 @@ SingleImagePickerPresenterDelegate>
[self showThemePicker];
}
}
else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX && row == userSettingsThreePidsInformation)
{
NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:0 inSection:SETTINGS_SECTION_DISCOVERY_INDEX];
[tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
else if (section == SETTINGS_SECTION_DISCOVERY_INDEX)
{
[self.settingsDiscoveryTableViewSection selectRow:indexPath.row];
}
else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX)
{
MXSession* session = [AppDelegate theDelegate].mxSessions[0];
@@ -4552,4 +4624,73 @@ SingleImagePickerPresenterDelegate>
[self refreshSettings];
}
#pragma mark - SettingsDiscoveryTableViewSectionDelegate
- (void)settingsDiscoveryTableViewSectionDidUpdate:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection
{
[self.tableView reloadData];
}
- (MXKTableViewCell *)settingsDiscoveryTableViewSection:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection tableViewCellClass:(Class)tableViewCellClass forRow:(NSInteger)forRow
{
MXKTableViewCell *tableViewCell;
if ([tableViewCellClass isEqual:[MXKTableViewCell class]])
{
tableViewCell = [self getDefaultTableViewCell:self.tableView];
}
else if ([tableViewCellClass isEqual:[MXKTableViewCellWithTextView class]])
{
tableViewCell = [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]];
}
else if ([tableViewCellClass isEqual:[MXKTableViewCellWithButton class]])
{
MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
if (!cell)
{
cell = [[MXKTableViewCellWithButton alloc] init];
}
else
{
// Fix https://github.com/vector-im/riot-ios/issues/1354
cell.mxkButton.titleLabel.text = nil;
}
cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
[cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
tableViewCell = cell;
}
else if ([tableViewCellClass isEqual:[MXKTableViewCellWithLabelAndSwitch class]])
{
tableViewCell = [self getLabelAndSwitchCell:self.tableView forIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]];
}
return tableViewCell;
}
#pragma mark - SettingsDiscoveryViewModelCoordinatorDelegate
- (void)settingsDiscoveryViewModel:(SettingsDiscoveryViewModel *)viewModel didSelectThreePidWith:(NSString *)medium and:(NSString *)address
{
SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter = [[SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession medium:medium adress:address];
MXWeakify(self);
[discoveryThreePidDetailsPresenter pushFrom:self.navigationController animated:YES popCompletion:^{
MXStrongifyAndReturnIfNil(self);
self.discoveryThreePidDetailsPresenter = nil;
}];
self.discoveryThreePidDetailsPresenter = discoveryThreePidDetailsPresenter;
}
- (void)settingsDiscoveryViewModelDidTapUserSettingsLink:(SettingsDiscoveryViewModel *)viewModel
{
NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX];
[self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
@end
@@ -14,3 +14,4 @@
#import "EncryptionInfoView.h"
#import "EventFormatter.h"
#import "MediaPickerViewController.h"
#import "AppDelegate.h"