Add support for account deactivation when not using OIDC. (#3295)

This commit is contained in:
Doug
2024-09-17 20:08:35 +01:00
committed by GitHub
parent 8660a22b3e
commit 5625e78fc9
41 changed files with 822 additions and 35 deletions
+41 -1
View File
@@ -48,6 +48,7 @@
07756D532EFE33DD1FA258E5 /* GeoURITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */; };
077CB230153E072C94B1E6C3 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D65BCC659FD9087E49B3C25 /* AppAppearance.swift */; };
07CC13C5729C24255348CBBD /* ElementCallWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */; };
07F6382E29845D235BFA3308 /* DeactivateAccountScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE78CAD0B964C66FD06EF83E /* DeactivateAccountScreenModels.swift */; };
086D01E79C8E8D3F004FAF21 /* AudioPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */; };
08CB4BD12CEEDE6AAE4A18DD /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035177BCD8E8308B098AC3C2 /* WindowManager.swift */; };
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
@@ -162,6 +163,7 @@
238D561CA231339C6D4D06F3 /* ClientBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1C33355FFB0F0953C35036 /* ClientBuilder.swift */; };
241CDEFE23819867D9B39066 /* RoomChangePermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */; };
244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */; };
244CB93DD7390379D905AFA8 /* DeactivateAccountScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E49D10BFA7E4D70A947888C /* DeactivateAccountScreen.swift */; };
24A1BBADAC43DC3F3A7347DA /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */; };
24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; };
24B7CD41342C143117ADA768 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B1CC9AA154F4D5435BF60A /* Comparable.swift */; };
@@ -333,6 +335,7 @@
4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */; };
4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24EC819497BB5F8C4998D760 /* RoomListFilterView.swift */; };
4AAA8606FBA290E23D15422E /* AvatarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */; };
4AD2B5426DBED97196AA4783 /* DeactivateAccountScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82EE3B877D91030248B1242D /* DeactivateAccountScreenViewModelProtocol.swift */; };
4B978C09567387EF4366BD7A /* MediaLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF1AC723C2609C7705569CA /* MediaLoaderTests.swift */; };
4BAB8222DBA0B4207D1223E0 /* NotificationSettingsProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */; };
4BB282209EA82015D0DF8F89 /* NavigationStackCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */; };
@@ -383,6 +386,7 @@
55CDD3968D95D1A820B5491E /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; };
55D18AA4F4A2257642EBDB94 /* GlobalSearchScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38354164AF59C5006CD05878 /* GlobalSearchScreenViewModel.swift */; };
562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */; };
564910A38858306301C1C21E /* DeactivateAccountScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1009E4A78F86DA42E1EAF0 /* DeactivateAccountScreenCoordinator.swift */; };
564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */; };
565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; };
56DACDD379A86A1F5DEFE7BE /* AuthenticationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */; };
@@ -571,6 +575,7 @@
8015842CB4DE1BE414D2CDED /* AppLockSetupBiometricsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C62E07C1164F5120727A2A8 /* AppLockSetupBiometricsScreenCoordinator.swift */; };
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; };
80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; };
80F6C8EFCA4564B67F0D34B0 /* DeactivateAccountScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */; };
81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; };
8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; };
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; };
@@ -893,6 +898,7 @@
C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */; };
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
C9A631FD968249B4BA0B7B3C /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EE0FABA8ED6D6C1D6CE71D /* ReactionsSummaryView.swift */; };
C9ABF75A43F2D26F1D9A1F27 /* DeactivateAccountScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AC3FDB58F57386741A4FC7F /* DeactivateAccountScreenViewModel.swift */; };
C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; };
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; };
CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; };
@@ -1321,6 +1327,7 @@
1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenModels.swift; sourceTree = "<group>"; };
1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
1DFE0E493FB55E5A62E7852A /* ProposedViewSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProposedViewSize.swift; sourceTree = "<group>"; };
1E49D10BFA7E4D70A947888C /* DeactivateAccountScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreen.swift; sourceTree = "<group>"; };
1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DTHTMLElement+AttributedStringBuilder.swift"; sourceTree = "<group>"; };
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilder.swift; sourceTree = "<group>"; };
1F7C6DDBB5D12F6EF6A3D6E1 /* CollapsibleReactionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleReactionLayout.swift; sourceTree = "<group>"; };
@@ -1380,6 +1387,7 @@
29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = "<group>"; };
2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = "<group>"; };
2AC3FDB58F57386741A4FC7F /* DeactivateAccountScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModel.swift; sourceTree = "<group>"; };
2AE807361805463F5AEDD1CA /* VoiceMessagePreviewComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessagePreviewComposer.swift; sourceTree = "<group>"; };
2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@@ -1745,6 +1753,7 @@
8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
82B612853BFB68373249777B /* SecureBackupKeyBackupScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenViewModel.swift; sourceTree = "<group>"; };
82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenCoordinator.swift; sourceTree = "<group>"; };
82EE3B877D91030248B1242D /* DeactivateAccountScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModelProtocol.swift; sourceTree = "<group>"; };
8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenViewModelProtocol.swift; sourceTree = "<group>"; };
837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModel.swift; sourceTree = "<group>"; };
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = "<group>"; };
@@ -1781,6 +1790,7 @@
897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = "<group>"; };
89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreen.swift; sourceTree = "<group>"; };
89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormButtonStyles.swift; sourceTree = "<group>"; };
8A1009E4A78F86DA42E1EAF0 /* DeactivateAccountScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenCoordinator.swift; sourceTree = "<group>"; };
8A9AE4967817E9608E22EB44 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersStateTests.swift; sourceTree = "<group>"; };
8AE78FA0011E07920AE83135 /* PlainMentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlainMentionBuilder.swift; sourceTree = "<group>"; };
@@ -1998,6 +2008,7 @@
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; };
BE78CAD0B964C66FD06EF83E /* DeactivateAccountScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenModels.swift; sourceTree = "<group>"; };
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = "<group>"; };
BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModel.swift; sourceTree = "<group>"; };
BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -2115,6 +2126,7 @@
D66B5D86A9AB95E0E01BED82 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConfiguration.swift; sourceTree = "<group>"; };
D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModelTests.swift; sourceTree = "<group>"; };
D79BB714D28C9F588DD69353 /* SecureBackupScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelProtocol.swift; sourceTree = "<group>"; };
D7BB243B26D54EF1A0C422C0 /* NotificationContentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentBuilder.swift; sourceTree = "<group>"; };
D7BEB970F500BFB248443FA1 /* BloomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloomView.swift; sourceTree = "<group>"; };
@@ -2446,6 +2458,14 @@
path = Logging;
sourceTree = "<group>";
};
07831A7BA411CC407B4727E2 /* View */ = {
isa = PBXGroup;
children = (
1E49D10BFA7E4D70A947888C /* DeactivateAccountScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
0787F81684E503024BD0C051 /* Services */ = {
isa = PBXGroup;
children = (
@@ -3640,6 +3660,18 @@
path = TimelineItemContent;
sourceTree = "<group>";
};
6C708A9F46EDE1105C640335 /* DeactivateAccountScreen */ = {
isa = PBXGroup;
children = (
8A1009E4A78F86DA42E1EAF0 /* DeactivateAccountScreenCoordinator.swift */,
BE78CAD0B964C66FD06EF83E /* DeactivateAccountScreenModels.swift */,
2AC3FDB58F57386741A4FC7F /* DeactivateAccountScreenViewModel.swift */,
82EE3B877D91030248B1242D /* DeactivateAccountScreenViewModelProtocol.swift */,
07831A7BA411CC407B4727E2 /* View */,
);
path = DeactivateAccountScreen;
sourceTree = "<group>";
};
6D7503E64A458DD09E65A3F7 /* View */ = {
isa = PBXGroup;
children = (
@@ -3769,6 +3801,7 @@
CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */,
69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */,
3B5E97E9615A158C76B2AB77 /* DateTests.swift */,
D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */,
6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */,
A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */,
099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */,
@@ -5171,6 +5204,7 @@
1185EECDD07495D65AC84AFC /* CallScreen */,
90DC2E28718955ED87AD1456 /* CreatePollScreen */,
C18958141C8ED6D778F779A4 /* CreateRoom */,
6C708A9F46EDE1105C640335 /* DeactivateAccountScreen */,
F5A65D1D3B83593598DC278D /* EmojiPickerScreen */,
8656AFF06650360A5D0695FF /* EncryptionReset */,
448435400B561C40E514BE1C /* FilePreviewScreen */,
@@ -6050,6 +6084,7 @@
0C932A5158C1D0604DFC5750 /* ComposerToolbarViewModelTests.swift in Sources */,
D3FD96913D2B1AAA3149DAC7 /* CreateRoomViewModelTests.swift in Sources */,
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */,
80F6C8EFCA4564B67F0D34B0 /* DeactivateAccountScreenViewModelTests.swift in Sources */,
864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */,
F697284B9B5F2C00CFEA3B12 /* EmojiDetectionTests.swift in Sources */,
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */,
@@ -6329,6 +6364,11 @@
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
9905C1B1C6EFE38F3A6533F3 /* Data.swift in Sources */,
C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */,
244CB93DD7390379D905AFA8 /* DeactivateAccountScreen.swift in Sources */,
564910A38858306301C1C21E /* DeactivateAccountScreenCoordinator.swift in Sources */,
07F6382E29845D235BFA3308 /* DeactivateAccountScreenModels.swift in Sources */,
C9ABF75A43F2D26F1D9A1F27 /* DeactivateAccountScreenViewModel.swift in Sources */,
4AD2B5426DBED97196AA4783 /* DeactivateAccountScreenViewModelProtocol.swift in Sources */,
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */,
5780E444F405AA1304E1C23E /* DeveloperOptionsScreen.swift in Sources */,
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */,
@@ -7847,7 +7887,7 @@
repositoryURL = "https://github.com/element-hq/compound-ios";
requirement = {
kind = revision;
revision = 22f9d801dd001e8aaed0f62546cdb42c7594cf92;
revision = a9270392b3269ef072c47dea623815a9fb87311d;
};
};
F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = {
@@ -15,7 +15,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/compound-ios",
"state" : {
"revision" : "22f9d801dd001e8aaed0f62546cdb42c7594cf92"
"revision" : "a9270392b3269ef072c47dea623815a9fb87311d"
}
},
{
@@ -33,12 +33,14 @@
"action_close" = "Close";
"action_complete_verification" = "Complete verification";
"action_confirm" = "Confirm";
"action_confirm_password" = "Confirm password";
"action_continue" = "Continue";
"action_copy" = "Copy";
"action_copy_link" = "Copy link";
"action_copy_link_to_message" = "Copy link to message";
"action_create" = "Create";
"action_create_a_room" = "Create a room";
"action_deactivate" = "Deactivate";
"action_decline" = "Decline";
"action_delete_poll" = "Delete Poll";
"action_disable" = "Disable";
@@ -107,6 +109,7 @@
"action_view_source" = "View source";
"action_yes" = "Yes";
"action.load_more" = "Load more";
"action_deactivate_account" = "Deactivate account";
"banner_migrate_to_native_sliding_sync_action" = "Log Out & Upgrade";
"banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later.";
"banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app.";
@@ -255,7 +258,7 @@
"emoji_picker_category_people" = "Smileys & People";
"emoji_picker_category_places" = "Travel & Places";
"emoji_picker_category_symbols" = "Symbols";
"error_account_creation_not_possible" = "Your homeserver needs to be upgraded to support Matrix Authentication Server and account creation.";
"error_account_creation_not_possible" = "Your homeserver needs to be upgraded to support Matrix Authentication Service and account creation.";
"error_failed_creating_the_permalink" = "Failed creating the permalink";
"error_failed_loading_map" = "%1$@ could not load the map. Please try again later.";
"error_failed_loading_messages" = "Failed loading messages";
@@ -336,7 +339,9 @@
"screen_resolve_send_failure_changed_identity_title" = "Your message was not sent because %1$@’s verified identity has changed";
"screen_resolve_send_failure_unsigned_device_primary_button_title" = "Send message anyway";
"screen_resolve_send_failure_unsigned_device_subtitle" = "%1$@ is using one or more unverified devices. You can send the message anyway, or you can cancel for now and try again later after %2$@ has verified all their devices.";
"screen_resolve_send_failure_unsigned_device_title" = "Your message was not sent because %1$@ has not verified one or more devices";
"screen_resolve_send_failure_unsigned_device_title" = "Your message was not sent because %1$@ has not verified all devices";
"screen_resolve_send_failure_you_unsigned_device_subtitle" = "One or more of your devices are unverified. You can send the message anyway, or you can cancel for now and try again later after you have verified all of your devices.";
"screen_resolve_send_failure_you_unsigned_device_title" = "Your message was not sent because you have not verified one or more of your devices";
"screen_room_mentions_at_room_subtitle" = "Notify the whole room";
"screen_room_pinned_banner_indicator" = "%1$@ of %2$@";
"screen_room_pinned_banner_indicator_description" = "%1$@ Pinned messages";
@@ -344,7 +349,8 @@
"screen_room_pinned_banner_view_all_button_title" = "View All";
"screen_room_details_pinned_events_row_title" = "Pinned messages";
"screen_timeline_item_menu_send_failure_changed_identity" = "Message not sent because %1$@’s verified identity has changed.";
"screen_timeline_item_menu_send_failure_unsigned_device" = "Message not sent because %1$@ has not verified one or more devices.";
"screen_timeline_item_menu_send_failure_unsigned_device" = "Message not sent because %1$@ has not verified all devices.";
"screen_timeline_item_menu_send_failure_you_unsigned_device" = "Message not sent because you have not verified one or more of your devices.";
"screen_account_provider_change" = "Change account provider";
"screen_account_provider_form_hint" = "Homeserver address";
"screen_account_provider_form_notice" = "Enter a search term or a domain address.";
@@ -451,6 +457,17 @@
"screen_create_room_public_option_description" = "Messages are not encrypted and anyone can read them. You can enable encryption at a later date.";
"screen_create_room_public_option_title" = "Public room (anyone)";
"screen_create_room_topic_label" = "Topic (optional)";
"screen_deactivate_account_confirmation_dialog_content" = "Please confirm that you want to deactivate your account. This action cannot be undone.";
"screen_deactivate_account_delete_all_messages" = "Delete all my messages";
"screen_deactivate_account_delete_all_messages_notice" = "Warning: Future users may see incomplete conversations.";
"screen_deactivate_account_description" = "Deactivating your account is %1$@, it will:";
"screen_deactivate_account_description_bold_part" = "irreversible";
"screen_deactivate_account_list_item_1" = "%1$@ your account (you can't log back in, and your ID can't be reused).";
"screen_deactivate_account_list_item_1_bold_part" = "Permanently disable";
"screen_deactivate_account_list_item_2" = "Remove you from all chat rooms.";
"screen_deactivate_account_list_item_3" = "Delete your account information from our identity server.";
"screen_deactivate_account_list_item_4" = "Your messages will still be visible to registered users but won’t be available to new or unregistered users if you choose to delete them.";
"screen_deactivate_account_title" = "Deactivate account";
"screen_edit_poll_delete_confirmation" = "Are you sure you want to delete this poll?";
"screen_edit_profile_display_name" = "Display name";
"screen_edit_profile_display_name_placeholder" = "Your display name";
@@ -126,6 +126,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
presentAdvancedSettings()
case .developerOptions:
presentDeveloperOptions()
case .deactivateAccount:
presentDeactivateAccount()
}
}
.store(in: &cancellables)
@@ -238,6 +240,30 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.push(coordinator)
}
private func presentDeactivateAccount() {
let navigationCoordinator = NavigationStackCoordinator()
let parameters = DeactivateAccountScreenCoordinatorParameters(clientProxy: parameters.userSession.clientProxy,
userIndicatorController: parameters.userIndicatorController)
let coordinator = DeactivateAccountScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
case .accountDeactivated:
actionsSubject.send(.forceLogout)
}
}
.store(in: &cancellables)
navigationCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(navigationCoordinator)
}
// MARK: OIDC Account Management
+41 -3
View File
@@ -96,6 +96,8 @@ internal enum L10n {
internal static var actionCompleteVerification: String { return L10n.tr("Localizable", "action_complete_verification") }
/// Confirm
internal static var actionConfirm: String { return L10n.tr("Localizable", "action_confirm") }
/// Confirm password
internal static var actionConfirmPassword: String { return L10n.tr("Localizable", "action_confirm_password") }
/// Continue
internal static var actionContinue: String { return L10n.tr("Localizable", "action_continue") }
/// Copy
@@ -108,6 +110,10 @@ internal enum L10n {
internal static var actionCreate: String { return L10n.tr("Localizable", "action_create") }
/// Create a room
internal static var actionCreateARoom: String { return L10n.tr("Localizable", "action_create_a_room") }
/// Deactivate
internal static var actionDeactivate: String { return L10n.tr("Localizable", "action_deactivate") }
/// Deactivate account
internal static var actionDeactivateAccount: String { return L10n.tr("Localizable", "action_deactivate_account") }
/// Decline
internal static var actionDecline: String { return L10n.tr("Localizable", "action_decline") }
/// Delete Poll
@@ -558,7 +564,7 @@ internal enum L10n {
internal static var emojiPickerCategoryPlaces: String { return L10n.tr("Localizable", "emoji_picker_category_places") }
/// Symbols
internal static var emojiPickerCategorySymbols: String { return L10n.tr("Localizable", "emoji_picker_category_symbols") }
/// Your homeserver needs to be upgraded to support Matrix Authentication Server and account creation.
/// Your homeserver needs to be upgraded to support Matrix Authentication Service and account creation.
internal static var errorAccountCreationNotPossible: String { return L10n.tr("Localizable", "error_account_creation_not_possible") }
/// Failed creating the permalink
internal static var errorFailedCreatingThePermalink: String { return L10n.tr("Localizable", "error_failed_creating_the_permalink") }
@@ -1041,6 +1047,32 @@ internal enum L10n {
internal static var screenCreateRoomTitle: String { return L10n.tr("Localizable", "screen_create_room_title") }
/// Topic (optional)
internal static var screenCreateRoomTopicLabel: String { return L10n.tr("Localizable", "screen_create_room_topic_label") }
/// Please confirm that you want to deactivate your account. This action cannot be undone.
internal static var screenDeactivateAccountConfirmationDialogContent: String { return L10n.tr("Localizable", "screen_deactivate_account_confirmation_dialog_content") }
/// Delete all my messages
internal static var screenDeactivateAccountDeleteAllMessages: String { return L10n.tr("Localizable", "screen_deactivate_account_delete_all_messages") }
/// Warning: Future users may see incomplete conversations.
internal static var screenDeactivateAccountDeleteAllMessagesNotice: String { return L10n.tr("Localizable", "screen_deactivate_account_delete_all_messages_notice") }
/// Deactivating your account is %1$@, it will:
internal static func screenDeactivateAccountDescription(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_deactivate_account_description", String(describing: p1))
}
/// irreversible
internal static var screenDeactivateAccountDescriptionBoldPart: String { return L10n.tr("Localizable", "screen_deactivate_account_description_bold_part") }
/// %1$@ your account (you can't log back in, and your ID can't be reused).
internal static func screenDeactivateAccountListItem1(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_deactivate_account_list_item_1", String(describing: p1))
}
/// Permanently disable
internal static var screenDeactivateAccountListItem1BoldPart: String { return L10n.tr("Localizable", "screen_deactivate_account_list_item_1_bold_part") }
/// Remove you from all chat rooms.
internal static var screenDeactivateAccountListItem2: String { return L10n.tr("Localizable", "screen_deactivate_account_list_item_2") }
/// Delete your account information from our identity server.
internal static var screenDeactivateAccountListItem3: String { return L10n.tr("Localizable", "screen_deactivate_account_list_item_3") }
/// Your messages will still be visible to registered users but won’t be available to new or unregistered users if you choose to delete them.
internal static var screenDeactivateAccountListItem4: String { return L10n.tr("Localizable", "screen_deactivate_account_list_item_4") }
/// Deactivate account
internal static var screenDeactivateAccountTitle: String { return L10n.tr("Localizable", "screen_deactivate_account_title") }
/// Block
internal static var screenDmDetailsBlockAlertAction: String { return L10n.tr("Localizable", "screen_dm_details_block_alert_action") }
/// Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime.
@@ -1475,10 +1507,14 @@ internal enum L10n {
internal static func screenResolveSendFailureUnsignedDeviceSubtitle(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "screen_resolve_send_failure_unsigned_device_subtitle", String(describing: p1), String(describing: p2))
}
/// Your message was not sent because %1$@ has not verified one or more devices
/// Your message was not sent because %1$@ has not verified all devices
internal static func screenResolveSendFailureUnsignedDeviceTitle(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_resolve_send_failure_unsigned_device_title", String(describing: p1))
}
/// One or more of your devices are unverified. You can send the message anyway, or you can cancel for now and try again later after you have verified all of your devices.
internal static var screenResolveSendFailureYouUnsignedDeviceSubtitle: String { return L10n.tr("Localizable", "screen_resolve_send_failure_you_unsigned_device_subtitle") }
/// Your message was not sent because you have not verified one or more of your devices
internal static var screenResolveSendFailureYouUnsignedDeviceTitle: String { return L10n.tr("Localizable", "screen_resolve_send_failure_you_unsigned_device_title") }
/// Failed to resolve room alias.
internal static var screenRoomAliasResolverResolveAliasFailure: String { return L10n.tr("Localizable", "screen_room_alias_resolver_resolve_alias_failure") }
/// Camera
@@ -1981,10 +2017,12 @@ internal enum L10n {
internal static func screenTimelineItemMenuSendFailureChangedIdentity(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_timeline_item_menu_send_failure_changed_identity", String(describing: p1))
}
/// Message not sent because %1$@ has not verified one or more devices.
/// Message not sent because %1$@ has not verified all devices.
internal static func screenTimelineItemMenuSendFailureUnsignedDevice(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_timeline_item_menu_send_failure_unsigned_device", String(describing: p1))
}
/// Message not sent because you have not verified one or more of your devices.
internal static var screenTimelineItemMenuSendFailureYouUnsignedDevice: String { return L10n.tr("Localizable", "screen_timeline_item_menu_send_failure_you_unsigned_device") }
/// Location
internal static var screenViewLocationTitle: String { return L10n.tr("Localizable", "screen_view_location_title") }
/// Calls, polls, search and more will be added later this year.
@@ -47,6 +47,7 @@ extension ClientProxyMock {
isOnlyDeviceLeftReturnValue = .success(false)
accountURLActionReturnValue = "https://matrix.org/account"
canDeactivateAccount = false
directRoomForUserIDReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
createDirectRoomWithExpectedRoomNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
@@ -1947,6 +1947,11 @@ class ClientProxyMock: ClientProxyProtocol {
}
var underlyingAvailableSlidingSyncVersions: [SlidingSyncVersion]!
var availableSlidingSyncVersionsClosure: (() async -> [SlidingSyncVersion])?
var canDeactivateAccount: Bool {
get { return underlyingCanDeactivateAccount }
set(value) { underlyingCanDeactivateAccount = value }
}
var underlyingCanDeactivateAccount: Bool!
var userIDServerName: String?
var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> {
get { return underlyingUserDisplayNamePublisher }
@@ -3209,6 +3214,76 @@ class ClientProxyMock: ClientProxyProtocol {
return sessionVerificationControllerProxyReturnValue
}
}
//MARK: - deactivateAccount
var deactivateAccountPasswordEraseDataUnderlyingCallsCount = 0
var deactivateAccountPasswordEraseDataCallsCount: Int {
get {
if Thread.isMainThread {
return deactivateAccountPasswordEraseDataUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = deactivateAccountPasswordEraseDataUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
deactivateAccountPasswordEraseDataUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
deactivateAccountPasswordEraseDataUnderlyingCallsCount = newValue
}
}
}
}
var deactivateAccountPasswordEraseDataCalled: Bool {
return deactivateAccountPasswordEraseDataCallsCount > 0
}
var deactivateAccountPasswordEraseDataReceivedArguments: (password: String?, eraseData: Bool)?
var deactivateAccountPasswordEraseDataReceivedInvocations: [(password: String?, eraseData: Bool)] = []
var deactivateAccountPasswordEraseDataUnderlyingReturnValue: Result<Void, ClientProxyError>!
var deactivateAccountPasswordEraseDataReturnValue: Result<Void, ClientProxyError>! {
get {
if Thread.isMainThread {
return deactivateAccountPasswordEraseDataUnderlyingReturnValue
} else {
var returnValue: Result<Void, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = deactivateAccountPasswordEraseDataUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
deactivateAccountPasswordEraseDataUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
deactivateAccountPasswordEraseDataUnderlyingReturnValue = newValue
}
}
}
}
var deactivateAccountPasswordEraseDataClosure: ((String?, Bool) async -> Result<Void, ClientProxyError>)?
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError> {
deactivateAccountPasswordEraseDataCallsCount += 1
deactivateAccountPasswordEraseDataReceivedArguments = (password: password, eraseData: eraseData)
DispatchQueue.main.async {
self.deactivateAccountPasswordEraseDataReceivedInvocations.append((password: password, eraseData: eraseData))
}
if let deactivateAccountPasswordEraseDataClosure = deactivateAccountPasswordEraseDataClosure {
return await deactivateAccountPasswordEraseDataClosure(password, eraseData)
} else {
return deactivateAccountPasswordEraseDataReturnValue
}
}
//MARK: - logout
var logoutUnderlyingCallsCount = 0
@@ -485,6 +485,71 @@ open class ClientSDKMock: MatrixRustSDK.Client {
}
}
//MARK: - canDeactivateAccount
var canDeactivateAccountUnderlyingCallsCount = 0
open var canDeactivateAccountCallsCount: Int {
get {
if Thread.isMainThread {
return canDeactivateAccountUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canDeactivateAccountUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canDeactivateAccountUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canDeactivateAccountUnderlyingCallsCount = newValue
}
}
}
}
open var canDeactivateAccountCalled: Bool {
return canDeactivateAccountCallsCount > 0
}
var canDeactivateAccountUnderlyingReturnValue: Bool!
open var canDeactivateAccountReturnValue: Bool! {
get {
if Thread.isMainThread {
return canDeactivateAccountUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canDeactivateAccountUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canDeactivateAccountUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canDeactivateAccountUnderlyingReturnValue = newValue
}
}
}
}
open var canDeactivateAccountClosure: (() -> Bool)?
open override func canDeactivateAccount() -> Bool {
canDeactivateAccountCallsCount += 1
if let canDeactivateAccountClosure = canDeactivateAccountClosure {
return canDeactivateAccountClosure()
} else {
return canDeactivateAccountReturnValue
}
}
//MARK: - createRoom
open var createRoomRequestThrowableError: Error?
@@ -560,6 +625,52 @@ open class ClientSDKMock: MatrixRustSDK.Client {
}
}
//MARK: - deactivateAccount
open var deactivateAccountAuthDataEraseDataThrowableError: Error?
var deactivateAccountAuthDataEraseDataUnderlyingCallsCount = 0
open var deactivateAccountAuthDataEraseDataCallsCount: Int {
get {
if Thread.isMainThread {
return deactivateAccountAuthDataEraseDataUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = deactivateAccountAuthDataEraseDataUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
deactivateAccountAuthDataEraseDataUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
deactivateAccountAuthDataEraseDataUnderlyingCallsCount = newValue
}
}
}
}
open var deactivateAccountAuthDataEraseDataCalled: Bool {
return deactivateAccountAuthDataEraseDataCallsCount > 0
}
open var deactivateAccountAuthDataEraseDataReceivedArguments: (authData: AuthData?, eraseData: Bool)?
open var deactivateAccountAuthDataEraseDataReceivedInvocations: [(authData: AuthData?, eraseData: Bool)] = []
open var deactivateAccountAuthDataEraseDataClosure: ((AuthData?, Bool) async throws -> Void)?
open override func deactivateAccount(authData: AuthData?, eraseData: Bool) async throws {
if let error = deactivateAccountAuthDataEraseDataThrowableError {
throw error
}
deactivateAccountAuthDataEraseDataCallsCount += 1
deactivateAccountAuthDataEraseDataReceivedArguments = (authData: authData, eraseData: eraseData)
DispatchQueue.main.async {
self.deactivateAccountAuthDataEraseDataReceivedInvocations.append((authData: authData, eraseData: eraseData))
}
try await deactivateAccountAuthDataEraseDataClosure?(authData, eraseData)
}
//MARK: - deletePusher
open var deletePusherIdentifiersThrowableError: Error?
@@ -0,0 +1,57 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Combine
import SwiftUI
struct DeactivateAccountScreenCoordinatorParameters {
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
enum DeactivateAccountScreenCoordinatorAction {
case cancel
case accountDeactivated
}
final class DeactivateAccountScreenCoordinator: CoordinatorProtocol {
private let parameters: DeactivateAccountScreenCoordinatorParameters
private let viewModel: DeactivateAccountScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<DeactivateAccountScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<DeactivateAccountScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: DeactivateAccountScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = DeactivateAccountScreenViewModel(clientProxy: parameters.clientProxy,
userIndicatorController: parameters.userIndicatorController)
}
func start() {
viewModel.actionsPublisher.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return }
switch action {
case .cancel:
actionsSubject.send(.cancel)
case .accountDeactivated:
actionsSubject.send(.accountDeactivated)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(DeactivateAccountScreen(context: viewModel.context))
}
}
@@ -0,0 +1,54 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
enum DeactivateAccountScreenViewModelAction {
case cancel
case accountDeactivated
}
struct DeactivateAccountScreenViewState: BindableState {
let info: AttributedString
let infoPoint1: AttributedString
let infoPoint2 = AttributedString(L10n.screenDeactivateAccountListItem2)
let infoPoint3 = AttributedString(L10n.screenDeactivateAccountListItem3)
let infoPoint4 = AttributedString(L10n.screenDeactivateAccountListItem4)
var bindings = DeactivateAccountScreenViewStateBindings()
init() {
let boldPlaceholder = "{bold}"
var attributedString = AttributedString(L10n.screenDeactivateAccountDescription(boldPlaceholder))
var boldString = AttributedString(L10n.screenDeactivateAccountDescriptionBoldPart)
boldString.bold()
attributedString.replace(boldPlaceholder, with: boldString)
info = attributedString
attributedString = AttributedString(L10n.screenDeactivateAccountListItem1(boldPlaceholder))
boldString = AttributedString(L10n.screenDeactivateAccountListItem1BoldPart)
boldString.bold()
attributedString.replace(boldPlaceholder, with: boldString)
infoPoint1 = attributedString
}
}
struct DeactivateAccountScreenViewStateBindings {
var password = ""
var eraseData = false
var alertInfo: AlertInfo<DeactivateAccountScreenAlert>?
}
enum DeactivateAccountScreenAlert {
case confirmation
case deactivationFailed
}
enum DeactivateAccountScreenViewAction {
case deactivate
case cancel
}
@@ -0,0 +1,84 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Combine
import SwiftUI
typealias DeactivateAccountScreenViewModelType = StateStoreViewModel<DeactivateAccountScreenViewState, DeactivateAccountScreenViewAction>
class DeactivateAccountScreenViewModel: DeactivateAccountScreenViewModelType, DeactivateAccountScreenViewModelProtocol {
private let clientProxy: ClientProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<DeactivateAccountScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<DeactivateAccountScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(clientProxy: ClientProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
self.clientProxy = clientProxy
self.userIndicatorController = userIndicatorController
super.init(initialViewState: DeactivateAccountScreenViewState())
}
override func process(viewAction: DeactivateAccountScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .cancel:
actionsSubject.send(.cancel)
case .deactivate:
showDeactivationConfirmation()
}
}
// MARK: - Private
private let deactivatingIndicatorID = "\(DeactivateAccountScreenViewModel.self)-Deactivating"
func showDeactivationConfirmation() {
state.bindings.alertInfo = .init(id: .confirmation,
title: L10n.screenDeactivateAccountTitle,
message: L10n.screenDeactivateAccountConfirmationDialogContent,
primaryButton: .init(title: L10n.actionDeactivate, action: {
Task { await self.deactivateAccount() }
}),
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
}
func deactivateAccount() async {
userIndicatorController.submitIndicator(UserIndicator(id: deactivatingIndicatorID,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false),
title: L10n.commonPleaseWait,
persistent: true))
MXLog.warning("Deactivating account.")
switch await clientProxy.deactivateAccount(password: nil, eraseData: state.bindings.eraseData) {
case .success:
MXLog.info("Account deactivated (no password needed).")
actionsSubject.send(.accountDeactivated)
return
case .failure:
MXLog.info("Request failed, including password.")
}
switch await clientProxy.deactivateAccount(password: state.bindings.password, eraseData: state.bindings.eraseData) {
case .success:
MXLog.info("Account deactivated.")
actionsSubject.send(.accountDeactivated)
return
case .failure(let failure):
MXLog.info("Deactivation failed \(failure).")
state.bindings.alertInfo = .init(id: .deactivationFailed,
title: L10n.errorUnknown,
message: String(describing: failure))
userIndicatorController.retractIndicatorWithId(deactivatingIndicatorID)
}
}
}
@@ -0,0 +1,14 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Combine
@MainActor
protocol DeactivateAccountScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<DeactivateAccountScreenViewModelAction, Never> { get }
var context: DeactivateAccountScreenViewModelType.Context { get }
}
@@ -0,0 +1,111 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Compound
import SwiftUI
struct DeactivateAccountScreen: View {
@ObservedObject var context: DeactivateAccountScreenViewModel.Context
var body: some View {
Form {
infoSection
eraseDataSection
passwordSection
}
.compoundList()
.safeAreaInset(edge: .bottom) {
Button(L10n.actionDeactivateAccount, role: .destructive) {
context.send(viewAction: .deactivate)
}
.buttonStyle(.compound(.primary))
.disabled(context.password.isEmpty)
.padding(16)
.background(Color.compound.bgSubtleSecondaryLevel0.ignoresSafeArea())
}
.navigationTitle(L10n.screenDeactivateAccountTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.alert(item: $context.alertInfo)
}
private var infoSection: some View {
ListRow(kind: .custom {
VStack(alignment: .leading, spacing: 16) {
Text(context.viewState.info)
VStack(alignment: .leading, spacing: 8) {
InfoItem(title: context.viewState.infoPoint1)
InfoItem(title: context.viewState.infoPoint2)
InfoItem(title: context.viewState.infoPoint3)
InfoItem(title: context.viewState.infoPoint4, isSuccess: true)
}
}
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.listRowBackground(Color.clear)
})
}
private var eraseDataSection: some View {
Section {
ListRow(label: .plain(title: L10n.screenDeactivateAccountDeleteAllMessages),
kind: .toggle($context.eraseData))
} footer: {
Text(L10n.screenDeactivateAccountDeleteAllMessagesNotice)
.compoundListSectionFooter()
}
}
private var passwordSection: some View {
Section {
ListRow(label: .plain(title: L10n.commonPassword),
kind: .secureField(text: $context.password))
.submitLabel(.done)
} header: {
Text(L10n.actionConfirmPassword)
.compoundListSectionHeader()
}
}
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
}
}
private struct InfoItem: View {
let title: AttributedString
var isSuccess = false
var body: some View {
Label {
Text(title).padding(.vertical, 1)
} icon: {
CompoundIcon(isSuccess ? \.check : \.close,
size: .small,
relativeTo: .compound.bodyMD)
.foregroundStyle(isSuccess ? .compound.iconSuccessPrimary : .compound.iconCriticalPrimary)
}
.labelStyle(.custom(spacing: 8, alignment: .top))
}
}
// MARK: - Previews
struct DeactivateAccountScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = DeactivateAccountScreenViewModel(clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
DeactivateAccountScreen(context: viewModel.context)
}
}
}
@@ -36,6 +36,7 @@ struct EncryptionResetPasswordScreen: View {
}
.buttonStyle(.compound(.primary))
}
.background()
.backgroundStyle(.compound.bgCanvasDefault)
.interactiveDismissDisabled()
.onAppear { textFieldFocus = true }
@@ -49,6 +50,7 @@ struct EncryptionResetPasswordScreen: View {
.font(.compound.bodySMSemibold)
SecureField(L10n.screenResetEncryptionPasswordPlaceholder, text: $context.password)
.tint(.compound.iconAccentTertiary)
.frame(maxWidth: .infinity)
.padding()
.background(Color.compound.bgSubtleSecondaryLevel0)
@@ -196,6 +196,7 @@ struct SecureBackupRecoveryKeyScreen: View {
.font(.compound.bodySMSemibold)
SecureField(L10n.screenRecoveryKeyConfirmKeyPlaceholder, text: $context.confirmationRecoveryKey)
.tint(.compound.iconAccentTertiary)
.frame(maxWidth: .infinity)
.padding()
.background(Color.compound.bgSubtleSecondaryLevel0)
@@ -27,6 +27,7 @@ enum SettingsScreenCoordinatorAction {
case notifications
case advancedSettings
case developerOptions
case deactivateAccount
}
final class SettingsScreenCoordinator: CoordinatorProtocol {
@@ -75,6 +76,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.developerOptions)
case .logout:
actionsSubject.send(.logout)
case .deactivateAccount:
actionsSubject.send(.deactivateAccount)
}
}
.store(in: &cancellables)
@@ -22,6 +22,7 @@ enum SettingsScreenViewModelAction: Equatable {
case advancedSettings
case developerOptions
case logout
case deactivateAccount
}
enum SettingsScreenSecuritySectionMode {
@@ -34,6 +35,7 @@ struct SettingsScreenViewState: BindableState {
var userID: String
var accountProfileURL: URL?
var accountSessionsListURL: URL?
var showAccountDeactivation: Bool
var userAvatarURL: URL?
var userDisplayName: String?
var showDeveloperOptions: Bool
@@ -42,6 +44,12 @@ struct SettingsScreenViewState: BindableState {
var showSecuritySectionBadge = false
var showBlockedUsers = false
var bindings = SettingsScreenViewStateBindings()
}
struct SettingsScreenViewStateBindings {
var isPresentingAccountDeactivationConfirmation = false
}
enum SettingsScreenViewAction {
@@ -59,4 +67,5 @@ enum SettingsScreenViewAction {
case developerOptions
case advancedSettings
case logout
case deactivateAccount
}
@@ -20,6 +20,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
init(userSession: UserSessionProtocol) {
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
userID: userSession.clientProxy.userID,
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
showDeveloperOptions: AppSettings.isDevelopmentBuild),
mediaProvider: userSession.mediaProvider)
@@ -105,6 +106,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
state.showDeveloperOptions = true
case .developerOptions:
actionsSubject.send(.developerOptions)
case .deactivateAccount:
actionsSubject.send(.deactivateAccount)
}
}
}
@@ -29,6 +29,8 @@ struct SettingsScreen: View {
}
generalSection
signOutSection
}
.compoundList()
.navigationTitle(L10n.commonSettings)
@@ -167,7 +169,11 @@ struct SettingsScreen: View {
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.developerOptions)
}
}
}
private var signOutSection: some View {
Section {
ListRow(label: .action(title: L10n.screenSignoutPreferenceItem,
icon: \.signOut,
role: .destructive),
@@ -175,6 +181,14 @@ struct SettingsScreen: View {
context.send(viewAction: .logout)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout)
if context.viewState.showAccountDeactivation {
ListRow(label: .action(title: L10n.actionDeactivateAccount,
icon: \.warning,
role: .destructive),
kind: .button {
context.send(viewAction: .deactivateAccount)
})
}
} footer: {
VStack(spacing: 0) {
versionText
@@ -213,6 +213,10 @@ class ClientProxy: ClientProxyProtocol {
}
}
var canDeactivateAccount: Bool {
client.canDeactivateAccount()
}
var userIDServerName: String? {
do {
return try client.userIdServerName()
@@ -546,6 +550,16 @@ class ClientProxy: ClientProxyProtocol {
}
}
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError> {
do {
try await client.deactivateAccount(authData: password.map { .password(passwordDetails: .init(identifier: userID, password: $0)) },
eraseData: eraseData)
return .success(())
} catch {
return .failure(.sdkError(error))
}
}
func setPusher(with configuration: PusherConfiguration) async throws {
try await client.setPusher(identifiers: configuration.identifiers,
kind: configuration.kind,
@@ -99,6 +99,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var slidingSyncVersion: SlidingSyncVersion { get }
var availableSlidingSyncVersions: [SlidingSyncVersion] { get async }
var canDeactivateAccount: Bool { get }
var userIDServerName: String? { get }
var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> { get }
@@ -157,6 +159,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
func logout() async -> URL?
func setPusher(with configuration: PusherConfiguration) async throws
+6
View File
@@ -147,6 +147,12 @@ class PreviewTests: XCTestCase {
}
}
func test_deactivateAccountScreen() {
for preview in DeactivateAccountScreen_Previews._allPreviews {
assertSnapshots(matching: preview)
}
}
func test_emojiPickerScreenHeaderView() {
for preview in EmojiPickerScreenHeaderView_Previews._allPreviews {
assertSnapshots(matching: preview)
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:49a19ce44c0cc157d621ad1d5067e9f0437646804778dae6b7090dac2a8d0cbe
size 168006
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f92b9a6497e4b1262ff279021ced2787d662e243e85e1a11d43e36d495cab9c8
size 214697
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:edd11721586663568ff973660f16fcff783dabba4bf5a2805caa0aa956ec2fd7
size 124025
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0f4b0057cb0a667fee987afdad9fe1bf2bc4ee3455e2e87f1a5eac98d51be364
size 192025
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4555eb5aab386a6dc73622b063a871f3d2c103e9078d14e0f65ec8d072d6cd0a
size 124524
oid sha256:89391abf747aef94c9b315e19607fd4256956d269d2f53141477952e604e8d24
size 123919
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:197d010abc66e31f74620898772fccdbe9b15e77ca72c764be65fca3e96ef5bd
size 160426
oid sha256:f58a9af3a9780ad49b7b5a90fa393b8f69bc1c752713477cbfb574389d101840
size 159142
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e6651a6be9527f47ede782f8a7f3a0c630884b4d2f14c34d67e3a7262bb39908
size 89346
oid sha256:c37c7425834dd88af07803bf1a414cfc6155957874cd2fe5a09fd6e17ff5458b
size 88086
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:90f15401000e28a427e45f5a3d2e4abaa6dd6074bdd541a04ad1ec5fc062b891
size 134865
oid sha256:63fbe1cf5d4c4b23b1f6fbe2b33d41c0d74654ccd33a473fd71527b7aa75a921
size 134230
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e5c825d5ed31fa05047c973d512e15d19e30840959115f821511587181a7c34
size 165789
oid sha256:86dae0a192710571d948d4bb45a7eecace0b7fcc242bd03692f34f1e2cdf493a
size 165901
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d367574abb9dd564373e2a701bff79b38a966ae58fe0d9ca2148bacd093a7369
size 183521
oid sha256:dbedd8dd0b052ed8fedf803adbc1eb0e91b4a0ea3b725cb771b63c6b906d0e12
size 183660
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b7f982a5b5ef3fa972445a1c8b8f71052a02bb8fcd00284b7767eb3f2e055547
size 106445
oid sha256:92933fcdfa65c428e73e8533bb2d53db209d52ee7cbb89668885fcde9326b60c
size 106451
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:227b46eab3e664ecb78f40031814589e6cfd0416c9babe8b0a4aaedfc45eabca
size 123175
oid sha256:5031a19fe97819af6b9de3c8a0b746e7b4b58293edb5c9a83549324057c3e6bf
size 123184
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:268ca2367cd6fae133b5aa193bac1516a4a5ed56268bb5b061453bd607501398
size 138558
oid sha256:6e0f5abc613626d47406cb2cdad815a735d3e3a1bc49a6e8202d7e11d4ec03a0
size 137485
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:25c106563019a4e83c4537f3063b8a18c23bde138ba724dc03d54f289ae8efbb
size 145432
oid sha256:459f29fc5cf3bdc3ab64ca8224235ed15ec2e81279a953f3dbd8b3564364ba16
size 144009
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74260e444cf6dffe7302066e81901b4e235d967a71586528e1a5a74691487f24
size 91442
oid sha256:766145a34e12c8d2bb0c9eb9ed70f0b72b5dca3268ce4fc9619ea3133b29a995
size 90291
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a212b2a93a757524ffc9085ac15e0c115add5b20f353507632d6eb7c794701b0
size 105467
oid sha256:69cc665b75d48187715e23e980891ad25b4a3ce201a852c55ab54827ad7dfdc9
size 103997
@@ -42,7 +42,7 @@ final class TemplateScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case .done:
self.actionsSubject.send(.done)
actionsSubject.send(.done)
}
}
.store(in: &cancellables)
@@ -0,0 +1,91 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import XCTest
@testable import ElementX
@MainActor
class DeactivateAccountScreenViewModelTests: XCTestCase {
var clientProxy: ClientProxyMock!
var viewModel: DeactivateAccountScreenViewModelProtocol!
var context: DeactivateAccountScreenViewModelType.Context {
viewModel.context
}
override func setUpWithError() throws {
clientProxy = ClientProxyMock(.init())
viewModel = DeactivateAccountScreenViewModel(clientProxy: clientProxy, userIndicatorController: UserIndicatorControllerMock())
}
func testDeactivate() async throws {
try await validateDeactivate(erasingData: false)
}
func testDeactivateAndErase() async throws {
try await validateDeactivate(erasingData: true)
}
func validateDeactivate(erasingData shouldErase: Bool) async throws {
let enteredPassword = UUID().uuidString
clientProxy.deactivateAccountPasswordEraseDataClosure = { [weak self] password, eraseData in
guard let self else { return .failure(.sdkError(ClientProxyMockError.generic)) }
if clientProxy.deactivateAccountPasswordEraseDataCallsCount == 1 {
if password != nil {
XCTFail("The password shouldn't be sent first time round.")
}
if eraseData != shouldErase {
XCTFail("The erase parameter is unexpected.")
}
return .failure(.sdkError(ClientProxyMockError.generic))
} else {
if password != enteredPassword {
XCTFail("The password should match the user's input on the second call.")
}
if eraseData != shouldErase {
XCTFail("The erase parameter is unexpected.")
}
return .success(())
}
}
context.eraseData = shouldErase
context.password = enteredPassword
XCTAssertNil(context.alertInfo)
let deferredState = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
context.send(viewAction: .deactivate)
try await deferredState.fulfill()
guard let confirmationAction = context.alertInfo?.primaryButton.action else {
XCTFail("Couldn't find the confirmation action.")
return
}
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { $0 == .accountDeactivated }
confirmationAction()
try await deferredAction.fulfill()
XCTAssertEqual(clientProxy.deactivateAccountPasswordEraseDataCallsCount, 2)
XCTAssertEqual(clientProxy.deactivateAccountPasswordEraseDataReceivedArguments?.password, enteredPassword)
XCTAssertEqual(clientProxy.deactivateAccountPasswordEraseDataReceivedArguments?.eraseData, shouldErase)
}
func testCancel() async throws {
// When cancelling the view.
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .cancel }
context.send(viewAction: .cancel)
try await deferred.fulfill()
// Then no API call should be made to deactivate the account.
XCTAssertFalse(clientProxy.deactivateAccountPasswordEraseDataCalled)
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ packages:
# path: ../matrix-rust-sdk
Compound:
url: https://github.com/element-hq/compound-ios
revision: 22f9d801dd001e8aaed0f62546cdb42c7594cf92
revision: a9270392b3269ef072c47dea623815a9fb87311d
# path: ../compound-ios
AnalyticsEvents:
url: https://github.com/matrix-org/matrix-analytics-events