From e3ad925285582516b4c848d8ba624e8f4fa340b6 Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:50:08 +0100 Subject: [PATCH] Security and privacy part 2 (#3637) * handling the history visibility flag * better logic to handle visibility * better handling of the visibility options state * added some copies, and the public room directory visibility state * completed the UI added also the preview tests * improved the handling of the directory visibility * added the space users case and improved handling of the access -> vsibility reaction. Also added a simple error handling for the public directory toggle * added the edit room address view but is missing its full implementation * implement the UI for the edit room address screen * implemented error checking when editing the address * updated preview tests and improved code * typo fix * Fix various issues after rebasing. * Fix build errors and broken snapshot tests * Adopt latest room privacy and canonical alias setting APIs * Add support for creating and editing the room's alias. * Add support for saving room privacy setting changes. * Fix room alias screen snapshot tests following recent changes. --------- Co-authored-by: Stefan Ceriu --- ElementX.xcodeproj/project.pbxproj | 46 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../RoomFlowCoordinator.swift | 26 +- .../Mocks/Generated/GeneratedMocks.swift | 548 ++++++++++++++++++ .../Mocks/Generated/SDKGeneratedMocks.swift | 529 +++++++++++++++-- .../Sources/Mocks/InvitedRoomProxyMock.swift | 3 +- .../Sources/Mocks/JoinedRoomProxyMock.swift | 5 +- .../Sources/Mocks/KnockedRoomProxyMock.swift | 3 +- .../Sources/Other/Extensions/String.swift | 10 + .../Views/EditRoomAddressListRow.swift | 64 ++ .../Screens/CreateRoom/CreateRoomModels.swift | 11 + .../CreateRoom/CreateRoomViewModel.swift | 16 +- .../CreateRoom/View/CreateRoomScreen.swift | 39 +- .../EditRoomAddressScreenCoordinator.swift | 55 ++ .../EditRoomAddressScreenModels.swift | 35 ++ .../EditRoomAddressScreenViewModel.swift | 174 ++++++ ...itRoomAddressScreenViewModelProtocol.swift | 14 + .../View/EditRoomAddressScreen.swift | 114 ++++ .../SecurityAndPrivacyScreenCoordinator.swift | 17 +- .../SecurityAndPrivacyScreenModels.swift | 56 +- .../SecurityAndPrivacyScreenViewModel.swift | 188 +++++- .../View/SecurityAndPrivacyScreen.swift | 160 ++++- .../Media/MediaUploadingPreprocessor.swift | 3 +- .../Services/Room/JoinedRoomProxy.swift | 83 +++ .../Sources/Services/Room/RoomInfoProxy.swift | 1 + .../Services/Room/RoomProxyProtocol.swift | 17 + .../Sources/GeneratedPreviewTests.swift | 12 + ...ressScreen-iPad-en-GB.Already-existing.png | 3 + ...dressScreen-iPad-en-GB.Invalid-symbols.png | 3 + ...tRoomAddressScreen-iPad-en-GB.No-alias.png | 3 + ...oomAddressScreen-iPad-en-GB.With-alias.png | 3 + ...essScreen-iPad-pseudo.Already-existing.png | 3 + ...ressScreen-iPad-pseudo.Invalid-symbols.png | 3 + ...RoomAddressScreen-iPad-pseudo.No-alias.png | 3 + ...omAddressScreen-iPad-pseudo.With-alias.png | 3 + ...creen-iPhone-16-en-GB.Already-existing.png | 3 + ...Screen-iPhone-16-en-GB.Invalid-symbols.png | 3 + ...AddressScreen-iPhone-16-en-GB.No-alias.png | 3 + ...dressScreen-iPhone-16-en-GB.With-alias.png | 3 + ...reen-iPhone-16-pseudo.Already-existing.png | 3 + ...creen-iPhone-16-pseudo.Invalid-symbols.png | 3 + ...ddressScreen-iPhone-16-pseudo.No-alias.png | 3 + ...ressScreen-iPhone-16-pseudo.With-alias.png | 3 + ...en-iPad-en-GB.Private-invite-only-room.png | 3 + ...iPad-en-GB.Public-room-without-address.png | 3 + ...ndPrivacyScreen-iPad-en-GB.Public-room.png | 3 + ...ivacyScreen-iPad-en-GB.Restricted-room.png | 3 + ...n-iPad-pseudo.Private-invite-only-room.png | 3 + ...Pad-pseudo.Public-room-without-address.png | 3 + ...dPrivacyScreen-iPad-pseudo.Public-room.png | 3 + ...vacyScreen-iPad-pseudo.Restricted-room.png | 3 + ...hone-16-en-GB.Private-invite-only-room.png | 3 + ...e-16-en-GB.Public-room-without-address.png | 3 + ...vacyScreen-iPhone-16-en-GB.Public-room.png | 3 + ...Screen-iPhone-16-en-GB.Restricted-room.png | 3 + ...one-16-pseudo.Private-invite-only-room.png | 3 + ...-16-pseudo.Public-room-without-address.png | 3 + ...acyScreen-iPhone-16-pseudo.Public-room.png | 3 + ...creen-iPhone-16-pseudo.Restricted-room.png | 3 + .../EditRoomAddressScreenViewModelTests.swift | 21 + project.yml | 2 +- 61 files changed, 2216 insertions(+), 136 deletions(-) create mode 100644 ElementX/Sources/Other/SwiftUI/Views/EditRoomAddressListRow.swift create mode 100644 ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenModels.swift create mode 100644 ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/EditRoomAddressScreen/View/EditRoomAddressScreen.swift create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Already-existing.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Invalid-symbols.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.No-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.With-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Already-existing.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Invalid-symbols.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.No-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.With-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Already-existing.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Invalid-symbols.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.No-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.With-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Already-existing.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Invalid-symbols.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.No-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.With-alias.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Private-invite-only-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room-without-address.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Restricted-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Private-invite-only-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room-without-address.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Restricted-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Private-invite-only-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room-without-address.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Restricted-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Private-invite-only-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room-without-address.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Restricted-room.png create mode 100644 UnitTests/Sources/EditRoomAddressScreenViewModelTests.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 09b60d194b..311de64fd3 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -227,6 +227,7 @@ 2BBA132149DEBED6624084A8 /* MessageForwardingScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */; }; 2BBC0EB1E07963810A5D7423 /* ReadMarkerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A284622B32052015F1F89 /* ReadMarkerRoomTimelineView.swift */; }; 2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */; }; + 2BC579CB5CE90CFE07CA0955 /* EditRoomAddressScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41656BC6267D55C56A2AAC08 /* EditRoomAddressScreenCoordinator.swift */; }; 2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */; }; 2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; }; 2CA61BB208CD82EBDB58CD13 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */; }; @@ -354,6 +355,7 @@ 4715FE33667C5899E64DD0E6 /* ResolveVerifiedUserSendFailureScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97287090CA64DAA95386ECED /* ResolveVerifiedUserSendFailureScreen.swift */; }; 4716587A9BA69ED8FD1B986B /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B19D10B102956066AF117B /* PollOptionView.swift */; }; 47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */; }; + 4764FC9A843D1F9865EDC29C /* EditRoomAddressScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.swift */; }; 478C363F63A5DFC3C83E334C /* MediaProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F65E4BB9E82EB8373207CF8 /* MediaProviderMock.swift */; }; 4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */; }; 4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; }; @@ -373,6 +375,7 @@ 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 */; }; + 4B25CDB4AA2C2AC0B4577217 /* EditRoomAddressListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2776E63E02719B20758EB78 /* EditRoomAddressListRow.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 */; }; @@ -386,6 +389,7 @@ 4D23D41B8109E010304050F8 /* QRCodeLoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA551A98778CEE7366838CE2 /* QRCodeLoginScreenCoordinator.swift */; }; 4D2B54233C7B2C04B4ABE55A /* EncryptionSettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */; }; 4D4D236F0BBCDC4D2CBCCBB5 /* RoomChangePermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C729D95CB4588D4D9AAC3DFA /* RoomChangePermissionsScreenModels.swift */; }; + 4D66CEBE490B2F118F4CEC8A /* EditRoomAddressScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD21AF0131AA38FF9534FAD /* EditRoomAddressScreenModels.swift */; }; 4DAEE2468669848B6C9F55B4 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33035418BB35754232985871 /* TimelineReadReceiptsView.swift */; }; 4DEEFB73181C3B023DB42686 /* NetworkMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */; }; 4E0D9E09B52CEC4C0E6211A8 /* MediaPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */; }; @@ -527,6 +531,7 @@ 69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; }; 69DE29C3E3180BB17D840690 /* ProgressCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97C8E13A1FBA717B0C277ECC /* ProgressCursorModifier.swift */; }; 6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A267106B9585D3D0CFC0D /* ClientError.swift */; }; + 6A38D0A2BC3943A92D82576E /* EditRoomAddressScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B18089ED50324583BB2FB7 /* EditRoomAddressScreenViewModelProtocol.swift */; }; 6A54F52443EC52AC5CD772C0 /* JoinRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869A8A4632E511351BFE2EC4 /* JoinRoomScreen.swift */; }; 6A916606A8B92FE8A990A219 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A1941B874A3BE9CDDF43EF /* XCTestCase.swift */; }; 6AD722DD92E465E56D2885AB /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */; }; @@ -814,6 +819,7 @@ A20364EE08D902E647C11FB3 /* WebRegistrationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D851A10FDA55579960DC61 /* WebRegistrationScreenCoordinator.swift */; }; A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */; }; A2172B5A26976F9174228B8A /* AppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */; }; + A2357AA4A188BC37085BC6F0 /* EditRoomAddressScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */; }; A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; }; A32384E3D85CA65342D3A908 /* TimelineMediaPreviewRedactConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75FE3F524B575D53787868C /* TimelineMediaPreviewRedactConfirmationView.swift */; }; @@ -1154,6 +1160,7 @@ ED564C8C7C43CF5F67000368 /* PlatformViewVersionPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26813CCE39221FE30BF22CD /* PlatformViewVersionPredicate.swift */; }; ED635D7F00FA07E94D3CE1E8 /* PreviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B796D347E53631576F631C /* PreviewTests.swift */; }; ED90A59F068FD0CA27E602ED /* UserProfileListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */; }; + EDB6915EC953BB2A44AA608E /* EditRoomAddressScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906451FB8CF27C628152BF7A /* EditRoomAddressScreenViewModelTests.swift */; }; EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */; }; EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */; }; EE56238683BC3ECA9BA00684 /* GlobalSearchScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */; }; @@ -1638,11 +1645,13 @@ 3F54FA7C5CB7B342EF9B9B2F /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; 40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManager.swift; sourceTree = ""; }; 40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelTests.swift; sourceTree = ""; }; + 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreen.swift; sourceTree = ""; }; 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceConstants.swift; sourceTree = ""; }; 40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoProxy.swift; sourceTree = ""; }; 40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 4100DDE6BF3C566AB66B80CC /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = ""; }; 4137900E28201C314C835C11 /* RoomScreenFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenFooterView.swift; sourceTree = ""; }; + 41656BC6267D55C56A2AAC08 /* EditRoomAddressScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenCoordinator.swift; sourceTree = ""; }; 4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = ""; }; 419957D7B1C983D7B3B93678 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = ""; }; 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionLogger.swift; sourceTree = ""; }; @@ -1999,6 +2008,7 @@ 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelTests.swift; sourceTree = ""; }; 8FB89DC7F9A4A91020037001 /* AuthenticationStartScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelTests.swift; sourceTree = ""; }; 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; + 906451FB8CF27C628152BF7A /* EditRoomAddressScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModelTests.swift; sourceTree = ""; }; 90791B9C739C716A40E1B230 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorder.swift; sourceTree = ""; }; 90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; @@ -2342,6 +2352,7 @@ D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConfiguration.swift; sourceTree = ""; }; D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModelTests.swift; sourceTree = ""; }; D79BB714D28C9F588DD69353 /* SecureBackupScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelProtocol.swift; sourceTree = ""; }; + D7B18089ED50324583BB2FB7 /* EditRoomAddressScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModelProtocol.swift; sourceTree = ""; }; D7BB243B26D54EF1A0C422C0 /* NotificationContentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentBuilder.swift; sourceTree = ""; }; D7BEB970F500BFB248443FA1 /* BloomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloomView.swift; sourceTree = ""; }; D879DC5515B1D42577F96C94 /* RoomSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSelectionScreen.swift; sourceTree = ""; }; @@ -2381,6 +2392,7 @@ E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = ""; }; + E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressListRow.swift; sourceTree = ""; }; E2B1CC9AA154F4D5435BF60A /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = ""; }; E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyMock.swift; sourceTree = ""; }; E3059CFA00C67D8787273B20 /* ServerSelectionScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenViewModel.swift; sourceTree = ""; }; @@ -2402,6 +2414,7 @@ E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; + E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModel.swift; sourceTree = ""; }; E60757AFE04391B43EA568B8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersView.swift; sourceTree = ""; }; E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = secrets.xcconfig; sourceTree = ""; }; @@ -2432,6 +2445,7 @@ EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = ""; }; EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenModels.swift; sourceTree = ""; }; EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModel.swift; sourceTree = ""; }; + EBD21AF0131AA38FF9534FAD /* EditRoomAddressScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenModels.swift; sourceTree = ""; }; EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = ""; }; EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = ""; }; ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSettingsFlowCoordinator.swift; sourceTree = ""; }; @@ -3213,6 +3227,7 @@ D01FD1171FF40E34D707FD00 /* BigIcon.swift */, 07934EF08BB39353E4A94272 /* BlurEffectView.swift */, 8CC23C63849452BC86EA2852 /* ButtonStyle.swift */, + E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */, B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */, C352359663A0E52BA20761EE /* LoadableImage.swift */, FFECCE59967018204876D0A5 /* LocationMarkerView.swift */, @@ -3586,6 +3601,18 @@ path = View; sourceTree = ""; }; + 45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */ = { + isa = PBXGroup; + children = ( + 41656BC6267D55C56A2AAC08 /* EditRoomAddressScreenCoordinator.swift */, + EBD21AF0131AA38FF9534FAD /* EditRoomAddressScreenModels.swift */, + E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */, + D7B18089ED50324583BB2FB7 /* EditRoomAddressScreenViewModelProtocol.swift */, + E0D6D3976CA2762529BAE7D3 /* View */, + ); + path = EditRoomAddressScreen; + sourceTree = ""; + }; 464C6BFAA853DC755B9C1F60 /* PinnedItemsBanner */ = { isa = PBXGroup; children = ( @@ -4129,6 +4156,7 @@ 3B5E97E9615A158C76B2AB77 /* DateTests.swift */, D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */, 6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */, + 906451FB8CF27C628152BF7A /* EditRoomAddressScreenViewModelTests.swift */, A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */, 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */, 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */, @@ -5562,6 +5590,14 @@ path = View; sourceTree = ""; }; + E0D6D3976CA2762529BAE7D3 /* View */ = { + isa = PBXGroup; + children = ( + 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.swift */, + ); + path = View; + sourceTree = ""; + }; E2DA161C142B7AB8CC40F752 /* Animation */ = { isa = PBXGroup; children = ( @@ -5595,6 +5631,7 @@ 90DC2E28718955ED87AD1456 /* CreatePollScreen */, C18958141C8ED6D778F779A4 /* CreateRoom */, 6C708A9F46EDE1105C640335 /* DeactivateAccountScreen */, + 45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */, F5A65D1D3B83593598DC278D /* EmojiPickerScreen */, 8656AFF06650360A5D0695FF /* EncryptionReset */, 448435400B561C40E514BE1C /* FilePreviewScreen */, @@ -6579,6 +6616,7 @@ CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */, 80F6C8EFCA4564B67F0D34B0 /* DeactivateAccountScreenViewModelTests.swift in Sources */, 864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */, + EDB6915EC953BB2A44AA608E /* EditRoomAddressScreenViewModelTests.swift in Sources */, F697284B9B5F2C00CFEA3B12 /* EmojiDetectionTests.swift in Sources */, 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */, 71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */, @@ -6906,6 +6944,12 @@ 037006FB6DF1374F94E4058D /* Dictionary.swift in Sources */, EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */, 4FDC8A9764CFDA90CE035725 /* Duration.swift in Sources */, + 4B25CDB4AA2C2AC0B4577217 /* EditRoomAddressListRow.swift in Sources */, + 4764FC9A843D1F9865EDC29C /* EditRoomAddressScreen.swift in Sources */, + 2BC579CB5CE90CFE07CA0955 /* EditRoomAddressScreenCoordinator.swift in Sources */, + 4D66CEBE490B2F118F4CEC8A /* EditRoomAddressScreenModels.swift in Sources */, + A2357AA4A188BC37085BC6F0 /* EditRoomAddressScreenViewModel.swift in Sources */, + 6A38D0A2BC3943A92D82576E /* EditRoomAddressScreenViewModelProtocol.swift in Sources */, 2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */, AE1160076F663BF14E0E893A /* EffectsView.swift in Sources */, FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */, @@ -8415,7 +8459,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "25.01.10-2"; + version = 25.01.13; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 992f28e6e6..5f7d9eb0cd 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "cc9cd80aa6954a7945d82c29182f81c5219f635e", - "version" : "25.1.10-2" + "revision" : "0e9afdf46c3128a0dc13c2d2fcee32ad3298a1a6", + "version" : "25.1.13" } }, { diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 536bb29c0a..cd215d7862 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -1468,14 +1468,16 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } private func presentSecurityAndPrivacyScreen() { - let coordinator = SecurityAndPrivacyScreenCoordinator(parameters: .init(roomProxy: roomProxy)) + let coordinator = SecurityAndPrivacyScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: userSession.clientProxy, + userIndicatorController: userIndicatorController)) coordinator.actionsPublisher.sink { [weak self] action in guard let self else { return } switch action { - case .done: - break + case .displayEditAddressScreen: + presentEditAddressScreen() } } .store(in: &cancellables) @@ -1485,6 +1487,24 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } } + private func presentEditAddressScreen() { + let stackCoordinator = NavigationStackCoordinator() + let coordinator = EditRoomAddressScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: userSession.clientProxy, + userIndicatorController: userIndicatorController)) + + coordinator.actionsPublisher.sink { [weak self] action in + switch action { + case .dismiss: + self?.navigationStackCoordinator.setSheetCoordinator(nil) + } + } + .store(in: &cancellables) + + stackCoordinator.setRootCoordinator(coordinator) + navigationStackCoordinator.setSheetCoordinator(stackCoordinator) + } + // MARK: - Other flows private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async { diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 1857b7f4a3..f92f9cda52 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -6223,6 +6223,70 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol { return messageFilteredTimelineAllowedMessageTypesReturnValue } } + //MARK: - enableEncryption + + var enableEncryptionUnderlyingCallsCount = 0 + var enableEncryptionCallsCount: Int { + get { + if Thread.isMainThread { + return enableEncryptionUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = enableEncryptionUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + enableEncryptionUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + enableEncryptionUnderlyingCallsCount = newValue + } + } + } + } + var enableEncryptionCalled: Bool { + return enableEncryptionCallsCount > 0 + } + + var enableEncryptionUnderlyingReturnValue: Result! + var enableEncryptionReturnValue: Result! { + get { + if Thread.isMainThread { + return enableEncryptionUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = enableEncryptionUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + enableEncryptionUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + enableEncryptionUnderlyingReturnValue = newValue + } + } + } + } + var enableEncryptionClosure: (() async -> Result)? + + func enableEncryption() async -> Result { + enableEncryptionCallsCount += 1 + if let enableEncryptionClosure = enableEncryptionClosure { + return await enableEncryptionClosure() + } else { + return enableEncryptionReturnValue + } + } //MARK: - redact var redactUnderlyingCallsCount = 0 @@ -7227,6 +7291,490 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol { return withdrawVerificationAndResendUserIDsSendHandleReturnValue } } + //MARK: - updateJoinRule + + var updateJoinRuleUnderlyingCallsCount = 0 + var updateJoinRuleCallsCount: Int { + get { + if Thread.isMainThread { + return updateJoinRuleUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateJoinRuleUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateJoinRuleUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateJoinRuleUnderlyingCallsCount = newValue + } + } + } + } + var updateJoinRuleCalled: Bool { + return updateJoinRuleCallsCount > 0 + } + var updateJoinRuleReceivedRule: JoinRule? + var updateJoinRuleReceivedInvocations: [JoinRule] = [] + + var updateJoinRuleUnderlyingReturnValue: Result! + var updateJoinRuleReturnValue: Result! { + get { + if Thread.isMainThread { + return updateJoinRuleUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = updateJoinRuleUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateJoinRuleUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + updateJoinRuleUnderlyingReturnValue = newValue + } + } + } + } + var updateJoinRuleClosure: ((JoinRule) async -> Result)? + + func updateJoinRule(_ rule: JoinRule) async -> Result { + updateJoinRuleCallsCount += 1 + updateJoinRuleReceivedRule = rule + DispatchQueue.main.async { + self.updateJoinRuleReceivedInvocations.append(rule) + } + if let updateJoinRuleClosure = updateJoinRuleClosure { + return await updateJoinRuleClosure(rule) + } else { + return updateJoinRuleReturnValue + } + } + //MARK: - updateHistoryVisibility + + var updateHistoryVisibilityUnderlyingCallsCount = 0 + var updateHistoryVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return updateHistoryVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateHistoryVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateHistoryVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateHistoryVisibilityUnderlyingCallsCount = newValue + } + } + } + } + var updateHistoryVisibilityCalled: Bool { + return updateHistoryVisibilityCallsCount > 0 + } + var updateHistoryVisibilityReceivedVisibility: RoomHistoryVisibility? + var updateHistoryVisibilityReceivedInvocations: [RoomHistoryVisibility] = [] + + var updateHistoryVisibilityUnderlyingReturnValue: Result! + var updateHistoryVisibilityReturnValue: Result! { + get { + if Thread.isMainThread { + return updateHistoryVisibilityUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = updateHistoryVisibilityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateHistoryVisibilityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + updateHistoryVisibilityUnderlyingReturnValue = newValue + } + } + } + } + var updateHistoryVisibilityClosure: ((RoomHistoryVisibility) async -> Result)? + + func updateHistoryVisibility(_ visibility: RoomHistoryVisibility) async -> Result { + updateHistoryVisibilityCallsCount += 1 + updateHistoryVisibilityReceivedVisibility = visibility + DispatchQueue.main.async { + self.updateHistoryVisibilityReceivedInvocations.append(visibility) + } + if let updateHistoryVisibilityClosure = updateHistoryVisibilityClosure { + return await updateHistoryVisibilityClosure(visibility) + } else { + return updateHistoryVisibilityReturnValue + } + } + //MARK: - isVisibleInRoomDirectory + + var isVisibleInRoomDirectoryUnderlyingCallsCount = 0 + var isVisibleInRoomDirectoryCallsCount: Int { + get { + if Thread.isMainThread { + return isVisibleInRoomDirectoryUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = isVisibleInRoomDirectoryUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + isVisibleInRoomDirectoryUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + isVisibleInRoomDirectoryUnderlyingCallsCount = newValue + } + } + } + } + var isVisibleInRoomDirectoryCalled: Bool { + return isVisibleInRoomDirectoryCallsCount > 0 + } + + var isVisibleInRoomDirectoryUnderlyingReturnValue: Result! + var isVisibleInRoomDirectoryReturnValue: Result! { + get { + if Thread.isMainThread { + return isVisibleInRoomDirectoryUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = isVisibleInRoomDirectoryUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + isVisibleInRoomDirectoryUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + isVisibleInRoomDirectoryUnderlyingReturnValue = newValue + } + } + } + } + var isVisibleInRoomDirectoryClosure: (() async -> Result)? + + func isVisibleInRoomDirectory() async -> Result { + isVisibleInRoomDirectoryCallsCount += 1 + if let isVisibleInRoomDirectoryClosure = isVisibleInRoomDirectoryClosure { + return await isVisibleInRoomDirectoryClosure() + } else { + return isVisibleInRoomDirectoryReturnValue + } + } + //MARK: - updateRoomDirectoryVisibility + + var updateRoomDirectoryVisibilityUnderlyingCallsCount = 0 + var updateRoomDirectoryVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return updateRoomDirectoryVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateRoomDirectoryVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateRoomDirectoryVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateRoomDirectoryVisibilityUnderlyingCallsCount = newValue + } + } + } + } + var updateRoomDirectoryVisibilityCalled: Bool { + return updateRoomDirectoryVisibilityCallsCount > 0 + } + var updateRoomDirectoryVisibilityReceivedVisibility: RoomVisibility? + var updateRoomDirectoryVisibilityReceivedInvocations: [RoomVisibility] = [] + + var updateRoomDirectoryVisibilityUnderlyingReturnValue: Result! + var updateRoomDirectoryVisibilityReturnValue: Result! { + get { + if Thread.isMainThread { + return updateRoomDirectoryVisibilityUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = updateRoomDirectoryVisibilityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateRoomDirectoryVisibilityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + updateRoomDirectoryVisibilityUnderlyingReturnValue = newValue + } + } + } + } + var updateRoomDirectoryVisibilityClosure: ((RoomVisibility) async -> Result)? + + func updateRoomDirectoryVisibility(_ visibility: RoomVisibility) async -> Result { + updateRoomDirectoryVisibilityCallsCount += 1 + updateRoomDirectoryVisibilityReceivedVisibility = visibility + DispatchQueue.main.async { + self.updateRoomDirectoryVisibilityReceivedInvocations.append(visibility) + } + if let updateRoomDirectoryVisibilityClosure = updateRoomDirectoryVisibilityClosure { + return await updateRoomDirectoryVisibilityClosure(visibility) + } else { + return updateRoomDirectoryVisibilityReturnValue + } + } + //MARK: - updateCanonicalAlias + + var updateCanonicalAliasAltAliasesUnderlyingCallsCount = 0 + var updateCanonicalAliasAltAliasesCallsCount: Int { + get { + if Thread.isMainThread { + return updateCanonicalAliasAltAliasesUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateCanonicalAliasAltAliasesUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateCanonicalAliasAltAliasesUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateCanonicalAliasAltAliasesUnderlyingCallsCount = newValue + } + } + } + } + var updateCanonicalAliasAltAliasesCalled: Bool { + return updateCanonicalAliasAltAliasesCallsCount > 0 + } + var updateCanonicalAliasAltAliasesReceivedArguments: (alias: String?, altAliases: [String])? + var updateCanonicalAliasAltAliasesReceivedInvocations: [(alias: String?, altAliases: [String])] = [] + + var updateCanonicalAliasAltAliasesUnderlyingReturnValue: Result! + var updateCanonicalAliasAltAliasesReturnValue: Result! { + get { + if Thread.isMainThread { + return updateCanonicalAliasAltAliasesUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = updateCanonicalAliasAltAliasesUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateCanonicalAliasAltAliasesUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + updateCanonicalAliasAltAliasesUnderlyingReturnValue = newValue + } + } + } + } + var updateCanonicalAliasAltAliasesClosure: ((String?, [String]) async -> Result)? + + func updateCanonicalAlias(_ alias: String?, altAliases: [String]) async -> Result { + updateCanonicalAliasAltAliasesCallsCount += 1 + updateCanonicalAliasAltAliasesReceivedArguments = (alias: alias, altAliases: altAliases) + DispatchQueue.main.async { + self.updateCanonicalAliasAltAliasesReceivedInvocations.append((alias: alias, altAliases: altAliases)) + } + if let updateCanonicalAliasAltAliasesClosure = updateCanonicalAliasAltAliasesClosure { + return await updateCanonicalAliasAltAliasesClosure(alias, altAliases) + } else { + return updateCanonicalAliasAltAliasesReturnValue + } + } + //MARK: - publishRoomAliasInRoomDirectory + + var publishRoomAliasInRoomDirectoryUnderlyingCallsCount = 0 + var publishRoomAliasInRoomDirectoryCallsCount: Int { + get { + if Thread.isMainThread { + return publishRoomAliasInRoomDirectoryUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = publishRoomAliasInRoomDirectoryUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + publishRoomAliasInRoomDirectoryUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + publishRoomAliasInRoomDirectoryUnderlyingCallsCount = newValue + } + } + } + } + var publishRoomAliasInRoomDirectoryCalled: Bool { + return publishRoomAliasInRoomDirectoryCallsCount > 0 + } + var publishRoomAliasInRoomDirectoryReceivedAlias: String? + var publishRoomAliasInRoomDirectoryReceivedInvocations: [String] = [] + + var publishRoomAliasInRoomDirectoryUnderlyingReturnValue: Result! + var publishRoomAliasInRoomDirectoryReturnValue: Result! { + get { + if Thread.isMainThread { + return publishRoomAliasInRoomDirectoryUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = publishRoomAliasInRoomDirectoryUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + publishRoomAliasInRoomDirectoryUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + publishRoomAliasInRoomDirectoryUnderlyingReturnValue = newValue + } + } + } + } + var publishRoomAliasInRoomDirectoryClosure: ((String) async -> Result)? + + func publishRoomAliasInRoomDirectory(_ alias: String) async -> Result { + publishRoomAliasInRoomDirectoryCallsCount += 1 + publishRoomAliasInRoomDirectoryReceivedAlias = alias + DispatchQueue.main.async { + self.publishRoomAliasInRoomDirectoryReceivedInvocations.append(alias) + } + if let publishRoomAliasInRoomDirectoryClosure = publishRoomAliasInRoomDirectoryClosure { + return await publishRoomAliasInRoomDirectoryClosure(alias) + } else { + return publishRoomAliasInRoomDirectoryReturnValue + } + } + //MARK: - removeRoomAliasFromRoomDirectory + + var removeRoomAliasFromRoomDirectoryUnderlyingCallsCount = 0 + var removeRoomAliasFromRoomDirectoryCallsCount: Int { + get { + if Thread.isMainThread { + return removeRoomAliasFromRoomDirectoryUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = removeRoomAliasFromRoomDirectoryUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + removeRoomAliasFromRoomDirectoryUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + removeRoomAliasFromRoomDirectoryUnderlyingCallsCount = newValue + } + } + } + } + var removeRoomAliasFromRoomDirectoryCalled: Bool { + return removeRoomAliasFromRoomDirectoryCallsCount > 0 + } + var removeRoomAliasFromRoomDirectoryReceivedAlias: String? + var removeRoomAliasFromRoomDirectoryReceivedInvocations: [String] = [] + + var removeRoomAliasFromRoomDirectoryUnderlyingReturnValue: Result! + var removeRoomAliasFromRoomDirectoryReturnValue: Result! { + get { + if Thread.isMainThread { + return removeRoomAliasFromRoomDirectoryUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = removeRoomAliasFromRoomDirectoryUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + removeRoomAliasFromRoomDirectoryUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + removeRoomAliasFromRoomDirectoryUnderlyingReturnValue = newValue + } + } + } + } + var removeRoomAliasFromRoomDirectoryClosure: ((String) async -> Result)? + + func removeRoomAliasFromRoomDirectory(_ alias: String) async -> Result { + removeRoomAliasFromRoomDirectoryCallsCount += 1 + removeRoomAliasFromRoomDirectoryReceivedAlias = alias + DispatchQueue.main.async { + self.removeRoomAliasFromRoomDirectoryReceivedInvocations.append(alias) + } + if let removeRoomAliasFromRoomDirectoryClosure = removeRoomAliasFromRoomDirectoryClosure { + return await removeRoomAliasFromRoomDirectoryClosure(alias) + } else { + return removeRoomAliasFromRoomDirectoryReturnValue + } + } //MARK: - flagAsUnread var flagAsUnreadUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index 0b5cfa5656..e0aa0a5af7 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -625,52 +625,6 @@ open class ClientSDKMock: MatrixRustSDK.Client { } } - //MARK: - createRoomAlias - - open var createRoomAliasRoomAliasRoomIdThrowableError: Error? - var createRoomAliasRoomAliasRoomIdUnderlyingCallsCount = 0 - open var createRoomAliasRoomAliasRoomIdCallsCount: Int { - get { - if Thread.isMainThread { - return createRoomAliasRoomAliasRoomIdUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = createRoomAliasRoomAliasRoomIdUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - createRoomAliasRoomAliasRoomIdUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - createRoomAliasRoomAliasRoomIdUnderlyingCallsCount = newValue - } - } - } - } - open var createRoomAliasRoomAliasRoomIdCalled: Bool { - return createRoomAliasRoomAliasRoomIdCallsCount > 0 - } - open var createRoomAliasRoomAliasRoomIdReceivedArguments: (roomAlias: String, roomId: String)? - open var createRoomAliasRoomAliasRoomIdReceivedInvocations: [(roomAlias: String, roomId: String)] = [] - open var createRoomAliasRoomAliasRoomIdClosure: ((String, String) async throws -> Void)? - - open override func createRoomAlias(roomAlias: String, roomId: String) async throws { - if let error = createRoomAliasRoomAliasRoomIdThrowableError { - throw error - } - createRoomAliasRoomAliasRoomIdCallsCount += 1 - createRoomAliasRoomAliasRoomIdReceivedArguments = (roomAlias: roomAlias, roomId: roomId) - DispatchQueue.main.async { - self.createRoomAliasRoomAliasRoomIdReceivedInvocations.append((roomAlias: roomAlias, roomId: roomId)) - } - try await createRoomAliasRoomAliasRoomIdClosure?(roomAlias, roomId) - } - //MARK: - customLoginWithJwt open var customLoginWithJwtJwtInitialDeviceNameDeviceIdThrowableError: Error? @@ -11326,6 +11280,46 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await editEventIdNewContentClosure?(eventId, newContent) } + //MARK: - enableEncryption + + open var enableEncryptionThrowableError: Error? + var enableEncryptionUnderlyingCallsCount = 0 + open var enableEncryptionCallsCount: Int { + get { + if Thread.isMainThread { + return enableEncryptionUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = enableEncryptionUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + enableEncryptionUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + enableEncryptionUnderlyingCallsCount = newValue + } + } + } + } + open var enableEncryptionCalled: Bool { + return enableEncryptionCallsCount > 0 + } + open var enableEncryptionClosure: (() async throws -> Void)? + + open override func enableEncryption() async throws { + if let error = enableEncryptionThrowableError { + throw error + } + enableEncryptionCallsCount += 1 + try await enableEncryptionClosure?() + } + //MARK: - enableSendQueue var enableSendQueueEnableUnderlyingCallsCount = 0 @@ -11437,6 +11431,75 @@ open class RoomSDKMock: MatrixRustSDK.Room { } } + //MARK: - getRoomVisibility + + open var getRoomVisibilityThrowableError: Error? + var getRoomVisibilityUnderlyingCallsCount = 0 + open var getRoomVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return getRoomVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = getRoomVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getRoomVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + getRoomVisibilityUnderlyingCallsCount = newValue + } + } + } + } + open var getRoomVisibilityCalled: Bool { + return getRoomVisibilityCallsCount > 0 + } + + var getRoomVisibilityUnderlyingReturnValue: RoomVisibility! + open var getRoomVisibilityReturnValue: RoomVisibility! { + get { + if Thread.isMainThread { + return getRoomVisibilityUnderlyingReturnValue + } else { + var returnValue: RoomVisibility? = nil + DispatchQueue.main.sync { + returnValue = getRoomVisibilityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getRoomVisibilityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + getRoomVisibilityUnderlyingReturnValue = newValue + } + } + } + } + open var getRoomVisibilityClosure: (() async throws -> RoomVisibility)? + + open override func getRoomVisibility() async throws -> RoomVisibility { + if let error = getRoomVisibilityThrowableError { + throw error + } + getRoomVisibilityCallsCount += 1 + if let getRoomVisibilityClosure = getRoomVisibilityClosure { + return try await getRoomVisibilityClosure() + } else { + return getRoomVisibilityReturnValue + } + } + //MARK: - hasActiveRoomCall var hasActiveRoomCallUnderlyingCallsCount = 0 @@ -13387,6 +13450,81 @@ open class RoomSDKMock: MatrixRustSDK.Room { } } + //MARK: - publishRoomAliasInRoomDirectory + + open var publishRoomAliasInRoomDirectoryAliasThrowableError: Error? + var publishRoomAliasInRoomDirectoryAliasUnderlyingCallsCount = 0 + open var publishRoomAliasInRoomDirectoryAliasCallsCount: Int { + get { + if Thread.isMainThread { + return publishRoomAliasInRoomDirectoryAliasUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = publishRoomAliasInRoomDirectoryAliasUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + publishRoomAliasInRoomDirectoryAliasUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + publishRoomAliasInRoomDirectoryAliasUnderlyingCallsCount = newValue + } + } + } + } + open var publishRoomAliasInRoomDirectoryAliasCalled: Bool { + return publishRoomAliasInRoomDirectoryAliasCallsCount > 0 + } + open var publishRoomAliasInRoomDirectoryAliasReceivedAlias: String? + open var publishRoomAliasInRoomDirectoryAliasReceivedInvocations: [String] = [] + + var publishRoomAliasInRoomDirectoryAliasUnderlyingReturnValue: Bool! + open var publishRoomAliasInRoomDirectoryAliasReturnValue: Bool! { + get { + if Thread.isMainThread { + return publishRoomAliasInRoomDirectoryAliasUnderlyingReturnValue + } else { + var returnValue: Bool? = nil + DispatchQueue.main.sync { + returnValue = publishRoomAliasInRoomDirectoryAliasUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + publishRoomAliasInRoomDirectoryAliasUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + publishRoomAliasInRoomDirectoryAliasUnderlyingReturnValue = newValue + } + } + } + } + open var publishRoomAliasInRoomDirectoryAliasClosure: ((String) async throws -> Bool)? + + open override func publishRoomAliasInRoomDirectory(alias: String) async throws -> Bool { + if let error = publishRoomAliasInRoomDirectoryAliasThrowableError { + throw error + } + publishRoomAliasInRoomDirectoryAliasCallsCount += 1 + publishRoomAliasInRoomDirectoryAliasReceivedAlias = alias + DispatchQueue.main.async { + self.publishRoomAliasInRoomDirectoryAliasReceivedInvocations.append(alias) + } + if let publishRoomAliasInRoomDirectoryAliasClosure = publishRoomAliasInRoomDirectoryAliasClosure { + return try await publishRoomAliasInRoomDirectoryAliasClosure(alias) + } else { + return publishRoomAliasInRoomDirectoryAliasReturnValue + } + } + //MARK: - rawName var rawNameUnderlyingCallsCount = 0 @@ -13538,6 +13676,81 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await removeAvatarClosure?() } + //MARK: - removeRoomAliasFromRoomDirectory + + open var removeRoomAliasFromRoomDirectoryAliasThrowableError: Error? + var removeRoomAliasFromRoomDirectoryAliasUnderlyingCallsCount = 0 + open var removeRoomAliasFromRoomDirectoryAliasCallsCount: Int { + get { + if Thread.isMainThread { + return removeRoomAliasFromRoomDirectoryAliasUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = removeRoomAliasFromRoomDirectoryAliasUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + removeRoomAliasFromRoomDirectoryAliasUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + removeRoomAliasFromRoomDirectoryAliasUnderlyingCallsCount = newValue + } + } + } + } + open var removeRoomAliasFromRoomDirectoryAliasCalled: Bool { + return removeRoomAliasFromRoomDirectoryAliasCallsCount > 0 + } + open var removeRoomAliasFromRoomDirectoryAliasReceivedAlias: String? + open var removeRoomAliasFromRoomDirectoryAliasReceivedInvocations: [String] = [] + + var removeRoomAliasFromRoomDirectoryAliasUnderlyingReturnValue: Bool! + open var removeRoomAliasFromRoomDirectoryAliasReturnValue: Bool! { + get { + if Thread.isMainThread { + return removeRoomAliasFromRoomDirectoryAliasUnderlyingReturnValue + } else { + var returnValue: Bool? = nil + DispatchQueue.main.sync { + returnValue = removeRoomAliasFromRoomDirectoryAliasUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + removeRoomAliasFromRoomDirectoryAliasUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + removeRoomAliasFromRoomDirectoryAliasUnderlyingReturnValue = newValue + } + } + } + } + open var removeRoomAliasFromRoomDirectoryAliasClosure: ((String) async throws -> Bool)? + + open override func removeRoomAliasFromRoomDirectory(alias: String) async throws -> Bool { + if let error = removeRoomAliasFromRoomDirectoryAliasThrowableError { + throw error + } + removeRoomAliasFromRoomDirectoryAliasCallsCount += 1 + removeRoomAliasFromRoomDirectoryAliasReceivedAlias = alias + DispatchQueue.main.async { + self.removeRoomAliasFromRoomDirectoryAliasReceivedInvocations.append(alias) + } + if let removeRoomAliasFromRoomDirectoryAliasClosure = removeRoomAliasFromRoomDirectoryAliasClosure { + return try await removeRoomAliasFromRoomDirectoryAliasClosure(alias) + } else { + return removeRoomAliasFromRoomDirectoryAliasReturnValue + } + } + //MARK: - reportContent open var reportContentEventIdScoreReasonThrowableError: Error? @@ -14863,6 +15076,144 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await unbanUserUserIdReasonClosure?(userId, reason) } + //MARK: - updateCanonicalAlias + + open var updateCanonicalAliasAliasAltAliasesThrowableError: Error? + var updateCanonicalAliasAliasAltAliasesUnderlyingCallsCount = 0 + open var updateCanonicalAliasAliasAltAliasesCallsCount: Int { + get { + if Thread.isMainThread { + return updateCanonicalAliasAliasAltAliasesUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateCanonicalAliasAliasAltAliasesUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateCanonicalAliasAliasAltAliasesUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateCanonicalAliasAliasAltAliasesUnderlyingCallsCount = newValue + } + } + } + } + open var updateCanonicalAliasAliasAltAliasesCalled: Bool { + return updateCanonicalAliasAliasAltAliasesCallsCount > 0 + } + open var updateCanonicalAliasAliasAltAliasesReceivedArguments: (alias: String?, altAliases: [String])? + open var updateCanonicalAliasAliasAltAliasesReceivedInvocations: [(alias: String?, altAliases: [String])] = [] + open var updateCanonicalAliasAliasAltAliasesClosure: ((String?, [String]) async throws -> Void)? + + open override func updateCanonicalAlias(alias: String?, altAliases: [String]) async throws { + if let error = updateCanonicalAliasAliasAltAliasesThrowableError { + throw error + } + updateCanonicalAliasAliasAltAliasesCallsCount += 1 + updateCanonicalAliasAliasAltAliasesReceivedArguments = (alias: alias, altAliases: altAliases) + DispatchQueue.main.async { + self.updateCanonicalAliasAliasAltAliasesReceivedInvocations.append((alias: alias, altAliases: altAliases)) + } + try await updateCanonicalAliasAliasAltAliasesClosure?(alias, altAliases) + } + + //MARK: - updateHistoryVisibility + + open var updateHistoryVisibilityVisibilityThrowableError: Error? + var updateHistoryVisibilityVisibilityUnderlyingCallsCount = 0 + open var updateHistoryVisibilityVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return updateHistoryVisibilityVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateHistoryVisibilityVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateHistoryVisibilityVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateHistoryVisibilityVisibilityUnderlyingCallsCount = newValue + } + } + } + } + open var updateHistoryVisibilityVisibilityCalled: Bool { + return updateHistoryVisibilityVisibilityCallsCount > 0 + } + open var updateHistoryVisibilityVisibilityReceivedVisibility: RoomHistoryVisibility? + open var updateHistoryVisibilityVisibilityReceivedInvocations: [RoomHistoryVisibility] = [] + open var updateHistoryVisibilityVisibilityClosure: ((RoomHistoryVisibility) async throws -> Void)? + + open override func updateHistoryVisibility(visibility: RoomHistoryVisibility) async throws { + if let error = updateHistoryVisibilityVisibilityThrowableError { + throw error + } + updateHistoryVisibilityVisibilityCallsCount += 1 + updateHistoryVisibilityVisibilityReceivedVisibility = visibility + DispatchQueue.main.async { + self.updateHistoryVisibilityVisibilityReceivedInvocations.append(visibility) + } + try await updateHistoryVisibilityVisibilityClosure?(visibility) + } + + //MARK: - updateJoinRules + + open var updateJoinRulesNewRuleThrowableError: Error? + var updateJoinRulesNewRuleUnderlyingCallsCount = 0 + open var updateJoinRulesNewRuleCallsCount: Int { + get { + if Thread.isMainThread { + return updateJoinRulesNewRuleUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateJoinRulesNewRuleUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateJoinRulesNewRuleUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateJoinRulesNewRuleUnderlyingCallsCount = newValue + } + } + } + } + open var updateJoinRulesNewRuleCalled: Bool { + return updateJoinRulesNewRuleCallsCount > 0 + } + open var updateJoinRulesNewRuleReceivedNewRule: JoinRule? + open var updateJoinRulesNewRuleReceivedInvocations: [JoinRule] = [] + open var updateJoinRulesNewRuleClosure: ((JoinRule) async throws -> Void)? + + open override func updateJoinRules(newRule: JoinRule) async throws { + if let error = updateJoinRulesNewRuleThrowableError { + throw error + } + updateJoinRulesNewRuleCallsCount += 1 + updateJoinRulesNewRuleReceivedNewRule = newRule + DispatchQueue.main.async { + self.updateJoinRulesNewRuleReceivedInvocations.append(newRule) + } + try await updateJoinRulesNewRuleClosure?(newRule) + } + //MARK: - updatePowerLevelsForUsers open var updatePowerLevelsForUsersUpdatesThrowableError: Error? @@ -14909,6 +15260,52 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await updatePowerLevelsForUsersUpdatesClosure?(updates) } + //MARK: - updateRoomVisibility + + open var updateRoomVisibilityVisibilityThrowableError: Error? + var updateRoomVisibilityVisibilityUnderlyingCallsCount = 0 + open var updateRoomVisibilityVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return updateRoomVisibilityVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateRoomVisibilityVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateRoomVisibilityVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateRoomVisibilityVisibilityUnderlyingCallsCount = newValue + } + } + } + } + open var updateRoomVisibilityVisibilityCalled: Bool { + return updateRoomVisibilityVisibilityCallsCount > 0 + } + open var updateRoomVisibilityVisibilityReceivedVisibility: RoomVisibility? + open var updateRoomVisibilityVisibilityReceivedInvocations: [RoomVisibility] = [] + open var updateRoomVisibilityVisibilityClosure: ((RoomVisibility) async throws -> Void)? + + open override func updateRoomVisibility(visibility: RoomVisibility) async throws { + if let error = updateRoomVisibilityVisibilityThrowableError { + throw error + } + updateRoomVisibilityVisibilityCallsCount += 1 + updateRoomVisibilityVisibilityReceivedVisibility = visibility + DispatchQueue.main.async { + self.updateRoomVisibilityVisibilityReceivedInvocations.append(visibility) + } + try await updateRoomVisibilityVisibilityClosure?(visibility) + } + //MARK: - uploadAvatar open var uploadAvatarMimeTypeDataMediaInfoThrowableError: Error? @@ -22276,6 +22673,46 @@ open class UserIdentitySDKMock: MatrixRustSDK.UserIdentity { pinCallsCount += 1 try await pinClosure?() } + + //MARK: - withdrawVerification + + open var withdrawVerificationThrowableError: Error? + var withdrawVerificationUnderlyingCallsCount = 0 + open var withdrawVerificationCallsCount: Int { + get { + if Thread.isMainThread { + return withdrawVerificationUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = withdrawVerificationUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + withdrawVerificationUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + withdrawVerificationUnderlyingCallsCount = newValue + } + } + } + } + open var withdrawVerificationCalled: Bool { + return withdrawVerificationCallsCount > 0 + } + open var withdrawVerificationClosure: (() async throws -> Void)? + + open override func withdrawVerification() async throws { + if let error = withdrawVerificationThrowableError { + throw error + } + withdrawVerificationCallsCount += 1 + try await withdrawVerificationClosure?() + } } open class WidgetDriverSDKMock: MatrixRustSDK.WidgetDriver { init() { diff --git a/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift b/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift index 4ee56269c4..bfc3d64d01 100644 --- a/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift @@ -60,7 +60,8 @@ extension RoomInfo { numUnreadNotifications: 0, numUnreadMentions: 0, pinnedEventIds: [], - joinRule: .invite) + joinRule: .invite, + historyVisibility: .shared) } } diff --git a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift index 16cb1ae5d8..ffb8fcf032 100644 --- a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift @@ -41,6 +41,7 @@ struct JoinedRoomProxyMockConfiguration { var shouldUseAutoUpdatingTimeline = false var joinRule: JoinRule? + var isVisibleInPublicDirectory = false } extension JoinedRoomProxyMock { @@ -127,6 +128,7 @@ extension JoinedRoomProxyMock { loadDraftReturnValue = .success(nil) clearDraftReturnValue = .success(()) sendTypingNotificationIsTypingReturnValue = .success(()) + isVisibleInRoomDirectoryReturnValue = .success(configuration.isVisibleInPublicDirectory) } } @@ -170,6 +172,7 @@ extension RoomInfo { numUnreadNotifications: 0, numUnreadMentions: 0, pinnedEventIds: Array(configuration.pinnedEventIDs), - joinRule: configuration.joinRule) + joinRule: configuration.joinRule, + historyVisibility: .shared) } } diff --git a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift index 55ae162fa9..241a446cd7 100644 --- a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift @@ -58,6 +58,7 @@ extension RoomInfo { numUnreadNotifications: 0, numUnreadMentions: 0, pinnedEventIds: [], - joinRule: .knock) + joinRule: .knock, + historyVisibility: .shared) } } diff --git a/ElementX/Sources/Other/Extensions/String.swift b/ElementX/Sources/Other/Extensions/String.swift index 4d7cdcaa1b..2e5e389590 100644 --- a/ElementX/Sources/Other/Extensions/String.swift +++ b/ElementX/Sources/Other/Extensions/String.swift @@ -97,3 +97,13 @@ extension String { trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } + +extension String { + static func makeCanonicalAlias(aliasLocalPart: Self?, serverName: Self?) -> Self? { + guard let aliasLocalPart, !aliasLocalPart.isEmpty, + let serverName, !serverName.isEmpty else { + return nil + } + return "#\(aliasLocalPart):\(serverName)" + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Views/EditRoomAddressListRow.swift b/ElementX/Sources/Other/SwiftUI/Views/EditRoomAddressListRow.swift new file mode 100644 index 0000000000..6d14c4ec5c --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/EditRoomAddressListRow.swift @@ -0,0 +1,64 @@ +// +// Copyright 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 EditRoomAddressListRow: View { + @Binding var aliasLocalPart: String + var serverName: String + var shouldDisplayError: Bool + + var body: some View { + ListRow(kind: .custom { + HStack(spacing: 0) { + Text("#") + .font(.compound.bodyLG) + .foregroundStyle(.compound.textSecondary) + TextField("", text: $aliasLocalPart) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .textContentType(.URL) + .tint(.compound.iconAccentTertiary) + .font(.compound.bodyLG) + .foregroundStyle(.compound.textPrimary) + .padding(.horizontal, 8) + Text(":\(serverName)") + .font(.compound.bodyLG) + .foregroundStyle(.compound.textSecondary) + } + .padding(ListRowPadding.textFieldInsets) + .environment(\.layoutDirection, .leftToRight) + .errorBackground(shouldDisplayError) + }) + } +} + +private extension View { + func errorBackground(_ shouldDisplay: Bool) -> some View { + listRowBackground(shouldDisplay ? AnyView(RoundedRectangle(cornerRadius: 10) + .inset(by: 1) + .fill(.compound.bgCriticalSubtleHovered) + .stroke(Color.compound.borderCriticalPrimary)) : AnyView(Color.compound.bgCanvasDefaultLevel1)) + } +} + +enum EditRoomAddressErrorState { + case alreadyExists + case invalidSymbols +} + +extension Set { + var errorDescription: String? { + if contains(.alreadyExists) { + return L10n.errorRoomAddressAlreadyExists + } else if contains(.invalidSymbols) { + return L10n.errorRoomAddressInvalidSymbols + } + return nil + } +} diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift index bdc023b600..4c6e831ead 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift @@ -72,3 +72,14 @@ enum CreateRoomAliasErrorState { case alreadyExists case invalidSymbols } + +extension Set { + var errorDescription: String? { + if contains(.alreadyExists) { + return L10n.errorRoomAddressAlreadyExists + } else if contains(.invalidSymbols) { + return L10n.errorRoomAddressInvalidSymbols + } + return nil + } +} diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index acfaa4cd3b..2a2bb25177 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -154,7 +154,8 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol guard state.isKnockingFeatureEnabled, !state.bindings.isRoomPrivate, - let canonicalAlias = canonicalAlias(aliasLocalPart: aliasLocalPart) else { + let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: aliasLocalPart, + serverName: state.serverName) else { // While is empty or private room we don't change or display the error return } @@ -209,8 +210,9 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol // Better to double check the errors also when trying to create the room if state.isKnockingFeatureEnabled, !createRoomParameters.isRoomPrivate { - guard let canonicalAlias = canonicalAlias(aliasLocalPart: createRoomParameters.aliasLocalPart), - isRoomAliasFormatValid(alias: canonicalAlias) else { + guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: createRoomParameters.aliasLocalPart, + serverName: state.serverName), + isRoomAliasFormatValid(alias: canonicalAlias) else { state.aliasErrors = [.invalidSymbols] return } @@ -271,14 +273,6 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol } } - func canonicalAlias(aliasLocalPart: String?) -> String? { - guard let aliasLocalPart, - !aliasLocalPart.isEmpty else { - return nil - } - return "#\(aliasLocalPart):\(state.serverName)" - } - // MARK: Loading indicator private static let loadingIndicatorIdentifier = "\(CreateRoomViewModel.self)-Loading" diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift index a7634d6846..03bab1b3aa 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift @@ -190,35 +190,17 @@ struct CreateRoomScreen: View { private var roomAliasSection: some View { Section { - ListRow(kind: .custom { - HStack(spacing: 0) { - Text("#") - .font(.compound.bodyLG) - .foregroundStyle(.compound.textSecondary) - TextField("", text: aliasBinding) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .textContentType(.URL) - .focused($focus, equals: .alias) - .tint(.compound.iconAccentTertiary) - .font(.compound.bodyLG) - .foregroundStyle(.compound.textPrimary) - .padding(.horizontal, 8) - Text(":\(context.viewState.serverName)") - .font(.compound.bodyLG) - .foregroundStyle(.compound.textSecondary) - } - .padding(ListRowPadding.textFieldInsets) - .environment(\.layoutDirection, .leftToRight) - .errorBackground(!context.viewState.aliasErrors.isEmpty) - }) - .id(Focus.alias) + EditRoomAddressListRow(aliasLocalPart: aliasBinding, + serverName: context.viewState.serverName, + shouldDisplayError: context.viewState.aliasErrors.errorDescription != nil) + .focused($focus, equals: .alias) + .id(Focus.alias) } header: { Text(L10n.screenCreateRoomRoomAddressSectionTitle) .compoundListSectionHeader() } footer: { VStack(alignment: .leading, spacing: 12) { - if let errorDescription = context.viewState.aliasErrorDescription { + if let errorDescription = context.viewState.aliasErrors.errorDescription { Label(errorDescription, icon: \.error, iconSize: .xSmall, relativeTo: .compound.bodySM) .foregroundStyle(.compound.textCriticalPrimary) .font(.compound.bodySM) @@ -241,15 +223,6 @@ struct CreateRoomScreen: View { } } -private extension View { - func errorBackground(_ shouldDisplay: Bool) -> some View { - listRowBackground(shouldDisplay ? AnyView(RoundedRectangle(cornerRadius: 10) - .inset(by: 1) - .fill(.compound.bgCriticalSubtleHovered) - .stroke(Color.compound.borderCriticalPrimary)) : AnyView(Color.compound.bgCanvasDefaultLevel1)) - } -} - // MARK: - Previews struct CreateRoom_Previews: PreviewProvider, TestablePreview { diff --git a/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenCoordinator.swift b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenCoordinator.swift new file mode 100644 index 0000000000..42f35ab4b7 --- /dev/null +++ b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenCoordinator.swift @@ -0,0 +1,55 @@ +// +// Copyright 2022-2024 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. +// + +// periphery:ignore:all - this is just a editRoomAddress remove this comment once generating the final file + +import Combine +import SwiftUI + +struct EditRoomAddressScreenCoordinatorParameters { + let roomProxy: JoinedRoomProxyProtocol + let clientProxy: ClientProxyProtocol + let userIndicatorController: UserIndicatorControllerProtocol +} + +enum EditRoomAddressScreenCoordinatorAction { + case dismiss +} + +final class EditRoomAddressScreenCoordinator: CoordinatorProtocol { + private let viewModel: EditRoomAddressScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: EditRoomAddressScreenCoordinatorParameters) { + viewModel = EditRoomAddressScreenViewModel(roomProxy: parameters.roomProxy, + 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 .dismiss: + actionsSubject.send(.dismiss) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(EditRoomAddressScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenModels.swift b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenModels.swift new file mode 100644 index 0000000000..1b969e4763 --- /dev/null +++ b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenModels.swift @@ -0,0 +1,35 @@ +// +// 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 EditRoomAddressScreenViewModelAction { + case dismiss +} + +struct EditRoomAddressScreenViewState: BindableState { + let serverName: String + var currentAliasLocalPart: String? + var aliasErrors: Set = [] + + var canSave: Bool { + currentAliasLocalPart != bindings.desiredAliasLocalPart && + aliasErrors.isEmpty && + !bindings.desiredAliasLocalPart.isEmpty + } + + var bindings: EditRoomAddressScreenViewStateBindings +} + +struct EditRoomAddressScreenViewStateBindings { + var desiredAliasLocalPart: String +} + +enum EditRoomAddressScreenViewAction { + case save + case cancel +} diff --git a/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModel.swift b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModel.swift new file mode 100644 index 0000000000..7ec9066dbe --- /dev/null +++ b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModel.swift @@ -0,0 +1,174 @@ +// +// 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 MatrixRustSDK +import SwiftUI + +typealias EditRoomAddressScreenViewModelType = StateStoreViewModel + +class EditRoomAddressScreenViewModel: EditRoomAddressScreenViewModelType, EditRoomAddressScreenViewModelProtocol { + let roomProxy: JoinedRoomProxyProtocol + let clientProxy: ClientProxyProtocol + let userIndicatorController: UserIndicatorControllerProtocol + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + @CancellableTask private var checkAliasAvailabilityTask: Task? + + init(initialViewState: EditRoomAddressScreenViewState? = nil, + roomProxy: JoinedRoomProxyProtocol, + clientProxy: ClientProxyProtocol, + userIndicatorController: UserIndicatorControllerProtocol) { + self.roomProxy = roomProxy + self.clientProxy = clientProxy + self.userIndicatorController = userIndicatorController + + var aliasLocalPart = "" + if let canonicalAlias = roomProxy.infoPublisher.value.canonicalAlias { + aliasLocalPart = canonicalAlias.dropFirst().split(separator: ":").first.flatMap(String.init) ?? "" + } else if let displayName = roomProxy.infoPublisher.value.displayName { + aliasLocalPart = roomAliasNameFromRoomDisplayName(roomName: displayName) + } + + if let initialViewState { + super.init(initialViewState: initialViewState) + } else { + super.init(initialViewState: EditRoomAddressScreenViewState(serverName: clientProxy.userIDServerName ?? "", + currentAliasLocalPart: aliasLocalPart, + bindings: .init(desiredAliasLocalPart: aliasLocalPart))) + } + setupSubscriptions() + } + + // MARK: - Public + + override func process(viewAction: EditRoomAddressScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .save: + Task { await save() } + case .cancel: + actionsSubject.send(.dismiss) + } + } + + private func setupSubscriptions() { + context.$viewState + .map(\.bindings.desiredAliasLocalPart) + .removeDuplicates() + .debounce(for: 0.5, scheduler: DispatchQueue.main) + .sink { [weak self] aliasLocalPart in + guard let self else { + return + } + + guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: aliasLocalPart, + serverName: state.serverName) else { + // While is empty don't display the errors, since the save button is already disabled + state.aliasErrors.removeAll() + return + } + + if !isRoomAliasFormatValid(alias: canonicalAlias) { + state.aliasErrors.insert(.invalidSymbols) + // If the alias is invalid we don't need to check for availability + state.aliasErrors.remove(.alreadyExists) + checkAliasAvailabilityTask = nil + return + } + + state.aliasErrors.remove(.invalidSymbols) + + guard aliasLocalPart != state.currentAliasLocalPart else { + // Doesn't make sense to check the availability and display an error if the alias didn't change, the save button should also be disabled + state.aliasErrors.remove(.alreadyExists) + checkAliasAvailabilityTask = nil + return + } + + checkAliasAvailabilityTask = Task { [weak self] in + guard let self else { + return + } + + if case .success(false) = await self.clientProxy.isAliasAvailable(canonicalAlias) { + guard !Task.isCancelled else { return } + state.aliasErrors.insert(.alreadyExists) + } else { + guard !Task.isCancelled else { return } + state.aliasErrors.remove(.alreadyExists) + } + } + } + .store(in: &cancellables) + } + + private func save() async { + showLoadingIndicator() + + defer { + hideLoadingIndicator() + } + + guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: state.bindings.desiredAliasLocalPart, serverName: state.serverName), + isRoomAliasFormatValid(alias: canonicalAlias) else { + state.aliasErrors = [.invalidSymbols] + return + } + + switch await clientProxy.isAliasAvailable(canonicalAlias) { + case .success(true): + break + case .success(false): + state.aliasErrors = [.alreadyExists] + return + case .failure: + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + return + } + + let oldAlias = roomProxy.infoPublisher.value.canonicalAlias + + // First publish the new alias + if case .failure = await roomProxy.publishRoomAliasInRoomDirectory(canonicalAlias) { + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + return + } + + // Then set it as the main alias + if case .failure = await roomProxy.updateCanonicalAlias(canonicalAlias, altAliases: []) { + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + return + } + + // And finally delete the old one + if let oldAlias, case .failure = await roomProxy.removeRoomAliasFromRoomDirectory(oldAlias) { + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + return + } + + actionsSubject.send(.dismiss) + } + + private static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading" + + private func showLoadingIndicator() { + userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) + } + + private func hideLoadingIndicator() { + userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + } +} diff --git a/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModelProtocol.swift b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModelProtocol.swift new file mode 100644 index 0000000000..d5cb0af592 --- /dev/null +++ b/ElementX/Sources/Screens/EditRoomAddressScreen/EditRoomAddressScreenViewModelProtocol.swift @@ -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 EditRoomAddressScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: EditRoomAddressScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/EditRoomAddressScreen/View/EditRoomAddressScreen.swift b/ElementX/Sources/Screens/EditRoomAddressScreen/View/EditRoomAddressScreen.swift new file mode 100644 index 0000000000..077131a8be --- /dev/null +++ b/ElementX/Sources/Screens/EditRoomAddressScreen/View/EditRoomAddressScreen.swift @@ -0,0 +1,114 @@ +// +// 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 EditRoomAddressScreen: View { + @ObservedObject var context: EditRoomAddressScreenViewModel.Context + + var body: some View { + Form { + Section { + EditRoomAddressListRow(aliasLocalPart: $context.desiredAliasLocalPart, + serverName: context.viewState.serverName, shouldDisplayError: context.viewState.aliasErrors.errorDescription != nil) + .onChange(of: context.desiredAliasLocalPart) { _, newAliasLocalPart in + context.desiredAliasLocalPart = newAliasLocalPart.lowercased() + } + .onSubmit { + if context.viewState.canSave { + context.send(viewAction: .save) + } + } + } footer: { + VStack(alignment: .leading, spacing: 12) { + if let errorDescription = context.viewState.aliasErrors.errorDescription { + Label(errorDescription, icon: \.error, iconSize: .xSmall, relativeTo: .compound.bodySM) + .foregroundStyle(.compound.textCriticalPrimary) + .font(.compound.bodySM) + } + Text(L10n.screenCreateRoomRoomAddressSectionFooter) + .compoundListSectionFooter() + .font(.compound.bodySM) + } + } + } + .compoundList() + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(L10n.screenEditRoomAddressTitle) + .toolbar { toolbar } + } + + @ToolbarContentBuilder + var toolbar: some ToolbarContent { + ToolbarItem(placement: .confirmationAction) { + Button(L10n.actionSave) { + context.send(viewAction: .save) + } + .disabled(!context.viewState.canSave) + } + ToolbarItem(placement: .cancellationAction) { + Button(L10n.actionCancel) { + context.send(viewAction: .cancel) + } + } + } +} + +// MARK: - Previews + +struct EditRoomAddressScreen_Previews: PreviewProvider, TestablePreview { + static let noAliasviewModel = EditRoomAddressScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Room Name")), + clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), + userIndicatorController: UserIndicatorControllerMock()) + + static let aliasviewModel = EditRoomAddressScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Room Name", canonicalAlias: "#room-alias:matrix.org")), + clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), + userIndicatorController: UserIndicatorControllerMock()) + + static let invalidSymbolsViewModel = EditRoomAddressScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Room Name", canonicalAlias: "#room#-alias:matrix.org")), + clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), + userIndicatorController: UserIndicatorControllerMock()) + + static let alreadyExistingViewModel = { + let clientProxy = ClientProxyMock(.init(userIDServerName: "matrix.org")) + clientProxy.isAliasAvailableReturnValue = .success(false) + return EditRoomAddressScreenViewModel(initialViewState: .init(serverName: "matrix.org", + bindings: .init(desiredAliasLocalPart: "whatever")), + roomProxy: JoinedRoomProxyMock(.init(name: "Room Name")), + clientProxy: clientProxy, + userIndicatorController: UserIndicatorControllerMock()) + }() + + static var previews: some View { + NavigationStack { + EditRoomAddressScreen(context: noAliasviewModel.context) + } + .previewDisplayName("No alias") + + NavigationStack { + EditRoomAddressScreen(context: aliasviewModel.context) + } + .previewDisplayName("With alias") + + NavigationStack { + EditRoomAddressScreen(context: invalidSymbolsViewModel.context) + } + .snapshotPreferences(expect: invalidSymbolsViewModel.context.$viewState.map { state in + !state.aliasErrors.isEmpty + }) + .previewDisplayName("Invalid symbols") + + NavigationStack { + EditRoomAddressScreen(context: alreadyExistingViewModel.context) + } + .snapshotPreferences(expect: alreadyExistingViewModel.context.$viewState.map { state in + !state.aliasErrors.isEmpty + }) + .previewDisplayName("Already existing") + } +} diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenCoordinator.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenCoordinator.swift index 7c63c9b6ac..b4622914c6 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenCoordinator.swift @@ -10,16 +10,15 @@ import SwiftUI struct SecurityAndPrivacyScreenCoordinatorParameters { let roomProxy: JoinedRoomProxyProtocol + let clientProxy: ClientProxyProtocol + let userIndicatorController: UserIndicatorControllerProtocol } enum SecurityAndPrivacyScreenCoordinatorAction { - case done - - // Consider adding CustomStringConvertible conformance if the actions contain PII + case displayEditAddressScreen } final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol { - private let parameters: SecurityAndPrivacyScreenCoordinatorParameters private let viewModel: SecurityAndPrivacyScreenViewModelProtocol private var cancellables = Set() @@ -30,9 +29,9 @@ final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol { } init(parameters: SecurityAndPrivacyScreenCoordinatorParameters) { - self.parameters = parameters - - viewModel = SecurityAndPrivacyScreenViewModel(roomProxy: parameters.roomProxy) + viewModel = SecurityAndPrivacyScreenViewModel(roomProxy: parameters.roomProxy, + clientProxy: parameters.clientProxy, + userIndicatorController: parameters.userIndicatorController) } func start() { @@ -41,8 +40,8 @@ final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol { guard let self else { return } switch action { - case .done: - actionsSubject.send(.done) + case .displayEditAddressScreen: + actionsSubject.send(.displayEditAddressScreen) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift index 211c62c025..bac1556c26 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift @@ -8,21 +8,44 @@ import Foundation enum SecurityAndPrivacyScreenViewModelAction { - case done + case displayEditAddressScreen } struct SecurityAndPrivacyScreenViewState: BindableState { - var bindings: SecurityAndPrivacyScreenViewStateBindings - + let serverName: String var currentSettings: SecurityAndPrivacySettings + var bindings: SecurityAndPrivacyScreenViewStateBindings + var canonicalAlias: String? - var hasChanges: Bool { + private var hasChanges: Bool { currentSettings != bindings.desiredSettings } - init(accessType: SecurityAndPrivacyRoomAccessType, - isEncryptionEnabled: Bool) { - let settings = SecurityAndPrivacySettings(accessType: accessType, isEncryptionEnabled: isEncryptionEnabled) + var isSaveDisabled: Bool { + !hasChanges || + (currentSettings.isVisibileInRoomDirectory == nil && + bindings.desiredSettings.accessType != .inviteOnly && + canonicalAlias != nil) + } + + var availableVisibilityOptions: [SecurityAndPrivacyHistoryVisibility] { + var options = [SecurityAndPrivacyHistoryVisibility.sinceSelection] + if !bindings.desiredSettings.isEncryptionEnabled, bindings.desiredSettings.accessType == .anyone { + options.append(.anyone) + } else { + options.append(.sinceInvite) + } + return options + } + + init(serverName: String, + accessType: SecurityAndPrivacyRoomAccessType, + isEncryptionEnabled: Bool, + historyVisibility: SecurityAndPrivacyHistoryVisibility) { + self.serverName = serverName + let settings = SecurityAndPrivacySettings(accessType: accessType, + isEncryptionEnabled: isEncryptionEnabled, + historyVisibility: historyVisibility) currentSettings = settings bindings = SecurityAndPrivacyScreenViewStateBindings(desiredSettings: settings) } @@ -36,12 +59,15 @@ struct SecurityAndPrivacyScreenViewStateBindings { struct SecurityAndPrivacySettings: Equatable { var accessType: SecurityAndPrivacyRoomAccessType var isEncryptionEnabled: Bool + var historyVisibility: SecurityAndPrivacyHistoryVisibility + var isVisibileInRoomDirectory: Bool? } enum SecurityAndPrivacyRoomAccessType { case inviteOnly case askToJoin case anyone + case spaceUsers } enum SecurityAndPrivacyAlertType { @@ -51,4 +77,20 @@ enum SecurityAndPrivacyAlertType { enum SecurityAndPrivacyScreenViewAction { case save case tryUpdatingEncryption(Bool) + case editAddress +} + +enum SecurityAndPrivacyHistoryVisibility { + case sinceSelection + case sinceInvite + case anyone + + var fallbackOption: Self { + switch self { + case .sinceInvite, .sinceSelection: + return .sinceSelection + case .anyone: + return .sinceInvite + } + } } diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift index cca0f71c6c..467844113d 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift @@ -6,22 +6,34 @@ // import Combine +import MatrixRustSDK import SwiftUI typealias SecurityAndPrivacyScreenViewModelType = StateStoreViewModel class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, SecurityAndPrivacyScreenViewModelProtocol { private let roomProxy: JoinedRoomProxyProtocol + private let clientProxy: ClientProxyProtocol + private let userIndicatorController: UserIndicatorControllerProtocol private let actionsSubject: PassthroughSubject = .init() var actionsPublisher: AnyPublisher { actionsSubject.eraseToAnyPublisher() } - - init(roomProxy: JoinedRoomProxyProtocol) { + + init(roomProxy: JoinedRoomProxyProtocol, + clientProxy: ClientProxyProtocol, + userIndicatorController: UserIndicatorControllerProtocol) { self.roomProxy = roomProxy - super.init(initialViewState: SecurityAndPrivacyScreenViewState(accessType: roomProxy.infoPublisher.value.roomAccessType, - isEncryptionEnabled: roomProxy.isEncrypted)) + self.clientProxy = clientProxy + self.userIndicatorController = userIndicatorController + super.init(initialViewState: SecurityAndPrivacyScreenViewState(serverName: clientProxy.userIDServerName ?? "", + accessType: roomProxy.infoPublisher.value.joinRule.toSecurityAndPrivacyRoomAccessType, + isEncryptionEnabled: roomProxy.isEncrypted, + historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility)) + + setupRoomDirectoryVisibility() + setupSubscriptions() } // MARK: - Public @@ -31,32 +43,184 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, switch viewAction { case .save: - actionsSubject.send(.done) + Task { + await saveDesiredSettings() + } case .tryUpdatingEncryption(let updatedValue): if updatedValue { state.bindings.alertInfo = .init(id: .enableEncryption, title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertTitle, message: L10n.screenSecurityAndPrivacyEnableEncryptionAlertDescription, - primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle) { [weak self] in - self?.state.bindings.desiredSettings.isEncryptionEnabled = true - }, + primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle) { [weak self] in self?.state.bindings.desiredSettings.isEncryptionEnabled = true }, secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) } else { state.bindings.desiredSettings.isEncryptionEnabled = false } + case .editAddress: + actionsSubject.send(.displayEditAddressScreen) + } + } + + // MARK: - Private + + private func setupSubscriptions() { + context.$viewState + .map(\.availableVisibilityOptions) + .removeDuplicates() + // To allow the view to update properly + .receive(on: DispatchQueue.main) + // When the available options changes always default to `sinceSelection` if the currently selected option is not available + .sink { [weak self] availableVisibilityOptions in + guard let self else { return } + let desiredHistoryVisibility = state.bindings.desiredSettings.historyVisibility + if !availableVisibilityOptions.contains(desiredHistoryVisibility) { + state.bindings.desiredSettings.historyVisibility = desiredHistoryVisibility.fallbackOption + } + } + .store(in: &cancellables) + + roomProxy.infoPublisher + .map(\.canonicalAlias) + .removeDuplicates() + .receive(on: DispatchQueue.main) + .weakAssign(to: \.state.canonicalAlias, on: self) + .store(in: &cancellables) + } + + private func setupRoomDirectoryVisibility() { + Task { + switch await roomProxy.isVisibleInRoomDirectory() { + case .success(let value): + state.bindings.desiredSettings.isVisibileInRoomDirectory = value + state.currentSettings.isVisibileInRoomDirectory = value + case .failure: + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + state.bindings.desiredSettings.isVisibileInRoomDirectory = false + state.currentSettings.isVisibileInRoomDirectory = false + } + } + } + + private func saveDesiredSettings() async { + showLoadingIndicator() + + defer { + hideLoadingIndicator() + } + + if state.currentSettings.isEncryptionEnabled != state.bindings.desiredSettings.isEncryptionEnabled { + switch await roomProxy.enableEncryption() { + case .success: + state.currentSettings.isEncryptionEnabled = state.bindings.desiredSettings.isEncryptionEnabled + case .failure: + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + } + } + + if state.currentSettings.historyVisibility != state.bindings.desiredSettings.historyVisibility { + switch await roomProxy.updateHistoryVisibility(state.bindings.desiredSettings.historyVisibility.toRoomHistoryVisibility) { + case .success: + state.currentSettings.historyVisibility = state.bindings.desiredSettings.historyVisibility + case .failure: + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + } + } + + if state.currentSettings.accessType != state.bindings.desiredSettings.accessType { + // When a user changes join rules to something other than knock or public, + // the room should be automatically made invisible (private) in the room directory. + if state.currentSettings.accessType != .askToJoin, state.currentSettings.accessType != .anyone { + state.bindings.desiredSettings.isVisibileInRoomDirectory = false + } + + switch await roomProxy.updateJoinRule(state.bindings.desiredSettings.accessType.toJoinRule) { + case .success: + state.currentSettings.accessType = state.bindings.desiredSettings.accessType + case .failure: + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + } + } + + if state.currentSettings.isVisibileInRoomDirectory != state.bindings.desiredSettings.isVisibileInRoomDirectory { + let visibility: RoomVisibility = state.bindings.desiredSettings.isVisibileInRoomDirectory == true ? .public : .private + + switch await roomProxy.updateRoomDirectoryVisibility(visibility) { + case .success: + state.currentSettings.isVisibileInRoomDirectory = state.bindings.desiredSettings.isVisibileInRoomDirectory + case .failure: + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + } + } + } + + private static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading" + + private func showLoadingIndicator() { + userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) + } + + private func hideLoadingIndicator() { + userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + } +} + +private extension SecurityAndPrivacyRoomAccessType { + var toJoinRule: JoinRule { + switch self { + case .inviteOnly: + .invite + case .askToJoin: + .knock + case .anyone: + .public + case .spaceUsers: + fatalError("The user shouldn't be able to select this rule") } } } -private extension RoomInfoProxy { - var roomAccessType: SecurityAndPrivacyRoomAccessType { - switch joinRule { - case .invite, .restricted: +private extension Optional where Wrapped == JoinRule { + var toSecurityAndPrivacyRoomAccessType: SecurityAndPrivacyRoomAccessType { + switch self { + case .none, .public: + return .anyone + case .invite: return .inviteOnly case .knock, .knockRestricted: return .askToJoin + case .restricted: + return .spaceUsers default: + return .inviteOnly + } + } +} + +private extension RoomHistoryVisibility { + var toSecurityAndPrivacyHistoryVisibility: SecurityAndPrivacyHistoryVisibility { + switch self { + case .joined, .invited: + return .sinceInvite + case .shared, .custom: + return .sinceSelection + case .worldReadable: return .anyone } } } + +private extension SecurityAndPrivacyHistoryVisibility { + var toRoomHistoryVisibility: RoomHistoryVisibility { + switch self { + case .sinceSelection: + return .shared + case .sinceInvite: + return .invited + case .anyone: + return .worldReadable + } + } +} diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift index 09dafe2c7b..0cbee785b5 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift @@ -14,7 +14,18 @@ struct SecurityAndPrivacyScreen: View { var body: some View { Form { roomAccessSection + if context.desiredSettings.accessType != .inviteOnly { + if let canonicalAlias = context.viewState.canonicalAlias { + visibilitySection + addressSection(canonicalAlias: canonicalAlias) + roomDirectoryVisibilitySection + } else { + publishingSection + addAddressSection + } + } encryptionSection + historySection } .compoundList() .navigationBarTitleDisplayMode(.inline) @@ -34,6 +45,14 @@ struct SecurityAndPrivacyScreen: View { ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle, description: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription), kind: .selection(isSelected: context.desiredSettings.accessType == .anyone) { context.desiredSettings.accessType = .anyone }) + + if context.viewState.currentSettings.accessType == .spaceUsers { + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle, + description: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionDescription), + kind: .selection(isSelected: context.desiredSettings.accessType == .spaceUsers) { }) + // This is not supported so it will always be disabled and has no handler + .disabled(true) + } } header: { Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader) .compoundListSectionHeader() @@ -42,7 +61,7 @@ struct SecurityAndPrivacyScreen: View { @ViewBuilder private var encryptionSection: some View { - let encryptionBinding = Binding(get: { + let binding = Binding(get: { context.desiredSettings.isEncryptionEnabled }, set: { newValue in context.send(viewAction: .tryUpdatingEncryption(newValue)) @@ -50,7 +69,7 @@ struct SecurityAndPrivacyScreen: View { Section { ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyEncryptionToggleTitle), - kind: .toggle(encryptionBinding)) + kind: .toggle(binding)) // We don't allow editing the encryption state if the current setting on the server is `enabled` .disabled(context.viewState.currentSettings.isEncryptionEnabled) } header: { @@ -62,26 +81,153 @@ struct SecurityAndPrivacyScreen: View { } } + private var historySection: some View { + Section { + ForEach(context.viewState.availableVisibilityOptions, id: \.self) { option in + switch option { + case .sinceSelection: + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceSelectingOptionTitle), + kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceSelection) { context.desiredSettings.historyVisibility = .sinceSelection }) + case .anyone: + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistoryAnyoneOptionTitle), + kind: .selection(isSelected: context.desiredSettings.historyVisibility == .anyone) { context.desiredSettings.historyVisibility = .anyone }) + case .sinceInvite: + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceInviteOptionTitle), + kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceInvite) { context.desiredSettings.historyVisibility = .sinceInvite }) + } + } + } header: { + Text(L10n.screenSecurityAndPrivacyRoomHistorySectionHeader) + .compoundListSectionHeader() + } + } + + private var visibilitySection: some View { + Section { + EmptyView() + } header: { + Text(L10n.screenSecurityAndPrivacyRoomVisibilitySectionHeader) + .compoundListSectionHeader() + } footer: { + Text(L10n.screenSecurityAndPrivacyRoomVisibilitySectionFooter(context.viewState.serverName)) + .compoundListSectionFooter() + } + } + + private func addressSection(canonicalAlias: String) -> some View { + Section { + ListRow(label: .plain(title: canonicalAlias), kind: .navigationLink { context.send(viewAction: .editAddress) }) + } header: { + Text(L10n.screenSecurityAndPrivacyRoomAddressSectionHeader) + .compoundListSectionHeader() + } footer: { + Text(L10n.screenSecurityAndPrivacyRoomAddressSectionFooter) + .compoundListSectionFooter() + } + } + + private var publishingSection: some View { + Section { + EmptyView() + } header: { + Text(L10n.screenSecurityAndPrivacyRoomPublishingSectionHeader) + .compoundListSectionHeader() + } footer: { + Text(L10n.screenSecurityAndPrivacyRoomPublishingSectionFooter) + .compoundListSectionFooter() + } + } + + private var addAddressSection: some View { + Section { + ListRow(kind: .custom { + Button(L10n.screenSecurityAndPrivacyAddRoomAddressAction) { context.send(viewAction: .editAddress) } + .foregroundColor(.compound.textActionAccent) + .padding(ListRowPadding.insets) + }) + } + } + + @ViewBuilder + private var roomDirectoryVisibilitySection: some View { + let binding = Binding(get: { + context.desiredSettings.isVisibileInRoomDirectory ?? false + }, set: { newValue in + context.desiredSettings.isVisibileInRoomDirectory = newValue + }) + + Section { + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomDirectoryVisibilityToggleTitle), + details: .isWaiting(context.desiredSettings.isVisibileInRoomDirectory == nil), + kind: context.desiredSettings.isVisibileInRoomDirectory == nil ? .label : .toggle(binding)) + } footer: { + Text(L10n.screenSecurityAndPrivacyRoomDirectoryVisibilitySectionFooter(context.viewState.serverName)) + .compoundListSectionFooter() + } + } + @ToolbarContentBuilder - var toolbar: some ToolbarContent { + private var toolbar: some ToolbarContent { ToolbarItem(placement: .confirmationAction) { Button(L10n.actionSave) { context.send(viewAction: .save) } - .disabled(!context.viewState.hasChanges) + .disabled(context.viewState.isSaveDisabled) } } } // MARK: - Previews -// TODO: Add back TestablePreview, this is WIP so running preview tests for it is not necessary -struct SecurityAndPrivacyScreen_Previews: PreviewProvider { - static let inviteOnlyViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(joinRule: .invite))) +struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { + static let inviteOnlyViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(joinRule: .invite)), + clientProxy: ClientProxyMock(.init()), + userIndicatorController: UserIndicatorControllerMock()) + + static let publicViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, + canonicalAlias: "#room:matrix.org", + joinRule: .public, + isVisibleInPublicDirectory: true)), + clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), + userIndicatorController: UserIndicatorControllerMock()) + + static let publicNoAddressViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, + joinRule: .public)), + clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), + userIndicatorController: UserIndicatorControllerMock()) + + static let restrictedViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, + canonicalAlias: "#room:matrix.org", + joinRule: .restricted(rules: []), + isVisibleInPublicDirectory: true)), + clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), + userIndicatorController: UserIndicatorControllerMock()) static var previews: some View { NavigationStack { SecurityAndPrivacyScreen(context: inviteOnlyViewModel.context) } + .previewDisplayName("Private invite only room") + + NavigationStack { + SecurityAndPrivacyScreen(context: publicViewModel.context) + } + .snapshotPreferences(expect: publicViewModel.context.$viewState.map { state in + state.currentSettings.isVisibileInRoomDirectory == true + }) + .previewDisplayName("Public room") + + NavigationStack { + SecurityAndPrivacyScreen(context: publicNoAddressViewModel.context) + } + .previewDisplayName("Public room without address") + + NavigationStack { + SecurityAndPrivacyScreen(context: restrictedViewModel.context) + } + .snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in + state.currentSettings.isVisibileInRoomDirectory == true + }) + .previewDisplayName("Restricted room") } } diff --git a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift index aa66186f2c..b298aca38e 100644 --- a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift +++ b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift @@ -175,7 +175,8 @@ struct MediaUploadingPreprocessor { size: fileSize, thumbnailInfo: thumbnailInfo, thumbnailSource: nil, - blurhash: thumbnailResult.blurhash) + blurhash: thumbnailResult.blurhash, + isAnimated: nil) let mediaInfo = MediaInfo.image(imageURL: url, thumbnailURL: thumbnailResult.url, imageInfo: imageInfo) diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index 5696e53948..b908355577 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -190,6 +190,16 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { } } + func enableEncryption() async -> Result { + do { + try await room.enableEncryption() + return .success(()) + } catch { + MXLog.error("Failed enabling encryption with error: \(error)") + return .failure(.sdkError(error)) + } + } + func redact(_ eventID: String) async -> Result { do { try await room.redact(eventId: eventID, reason: nil) @@ -359,6 +369,79 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { } } + // MARK: - Privacy settings + + func updateJoinRule(_ rule: JoinRule) async -> Result { + do { + try await room.updateJoinRules(newRule: rule) + return .success(()) + } catch { + MXLog.error("Failed updating join rule with error: \(error)") + return .failure(.sdkError(error)) + } + } + + func updateHistoryVisibility(_ visibility: RoomHistoryVisibility) async -> Result { + do { + try await room.updateHistoryVisibility(visibility: visibility) + return .success(()) + } catch { + MXLog.error("Failed updating history visibility with error: \(error)") + return .failure(.sdkError(error)) + } + } + + func isVisibleInRoomDirectory() async -> Result { + do { + return try await .success(room.getRoomVisibility() == .public) + } catch { + MXLog.error("Failed checking if room is visible in room directory with error: \(error)") + return .failure(.sdkError(error)) + } + } + + func updateRoomDirectoryVisibility(_ visibility: RoomVisibility) async -> Result { + do { + try await room.updateRoomVisibility(visibility: visibility) + return .success(()) + } catch { + MXLog.error("Failed updating room directory visibility with error: \(error)") + return .failure(.sdkError(error)) + } + } + + // MARK: - Canonical Alias + + func updateCanonicalAlias(_ alias: String?, altAliases: [String]) async -> Result { + do { + try await room.updateCanonicalAlias(alias: alias, altAliases: altAliases) + return .success(()) + } catch { + MXLog.error("Failed updating canonical alias with error: \(error)") + return .failure(.sdkError(error)) + } + } + + func publishRoomAliasInRoomDirectory(_ alias: String) async -> Result { + do { + let result = try await room.publishRoomAliasInRoomDirectory(alias: alias) + return .success(result) + } catch { + MXLog.error("Failed publishing the room's alias in the room directory with error: \(error)") + return .failure(.sdkError(error)) + } + } + + func removeRoomAliasFromRoomDirectory(_ alias: String) async -> Result { + do { + let result = try await room.removeRoomAliasFromRoomDirectory(alias: alias) + return .success(result) + } catch { + MXLog.error("Failed removing the room's alias in the room directory with error: \(error)") + return .failure(.sdkError(error)) + } + } + // MARK: - Room flags func flagAsUnread(_ isUnread: Bool) async -> Result { diff --git a/ElementX/Sources/Services/Room/RoomInfoProxy.swift b/ElementX/Sources/Services/Room/RoomInfoProxy.swift index 489ba83c9a..f1ab846513 100644 --- a/ElementX/Sources/Services/Room/RoomInfoProxy.swift +++ b/ElementX/Sources/Services/Room/RoomInfoProxy.swift @@ -66,6 +66,7 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol { var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) } var pinnedEventIDs: Set { Set(roomInfo.pinnedEventIds) } var joinRule: JoinRule? { roomInfo.joinRule } + var historyVisibility: RoomHistoryVisibility { roomInfo.historyVisibility } } struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol { diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 1a3d9f9d77..de026cbd72 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -79,6 +79,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result + func enableEncryption() async -> Result + func redact(_ eventID: String) async -> Result func reportContent(_ eventID: String, reason: String?) async -> Result @@ -110,6 +112,21 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { func withdrawVerificationAndResend(userIDs: [String], sendHandle: SendHandleProxy) async -> Result + // MARK: - Privacy settings + + func updateJoinRule(_ rule: JoinRule) async -> Result + func updateHistoryVisibility(_ visibility: RoomHistoryVisibility) async -> Result + + func isVisibleInRoomDirectory() async -> Result + func updateRoomDirectoryVisibility(_ visibility: RoomVisibility) async -> Result + + // MARK: - Canonical Alias + + func updateCanonicalAlias(_ alias: String?, altAliases: [String]) async -> Result + + func publishRoomAliasInRoomDirectory(_ alias: String) async -> Result + func removeRoomAliasFromRoomDirectory(_ alias: String) async -> Result + // MARK: - Room Flags func flagAsUnread(_ isUnread: Bool) async -> Result diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index f2539e99f0..abf9836e42 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -143,6 +143,12 @@ extension PreviewTests { } } + func test_editRoomAddressScreen() async throws { + for preview in EditRoomAddressScreen_Previews._allPreviews { + try await assertSnapshots(matching: preview) + } + } + func test_emojiPickerScreenHeaderView() async throws { for preview in EmojiPickerScreenHeaderView_Previews._allPreviews { try await assertSnapshots(matching: preview) @@ -785,6 +791,12 @@ extension PreviewTests { } } + func test_securityAndPrivacyScreen() async throws { + for preview in SecurityAndPrivacyScreen_Previews._allPreviews { + try await assertSnapshots(matching: preview) + } + } + func test_separatorMediaEventsTimelineView() async throws { for preview in SeparatorMediaEventsTimelineView_Previews._allPreviews { try await assertSnapshots(matching: preview) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Already-existing.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Already-existing.png new file mode 100644 index 0000000000..77751b021f --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Already-existing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed820a7cbe2fee94312fdc4300be84340e2b83c420617785236966591f7b0ad8 +size 115635 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Invalid-symbols.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Invalid-symbols.png new file mode 100644 index 0000000000..d1cf09caee --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.Invalid-symbols.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:084b4cea984f0b2952cb57776278d6866260b6fa72b472c68508b46f6be9d0fc +size 120798 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.No-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.No-alias.png new file mode 100644 index 0000000000..71a688937a --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.No-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd9afbafee47a33dfa211d290b3737873fb55e8706ec7b6cf3c74f1820da86ce +size 103048 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.With-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.With-alias.png new file mode 100644 index 0000000000..e9613909eb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-en-GB.With-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7beccc3c1111498a63be05cb11da67baf56146c893f9fa329089dd375ce69a5 +size 103075 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Already-existing.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Already-existing.png new file mode 100644 index 0000000000..7c7753e7cc --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Already-existing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:584ca7225cf3d8e129ac815d5893ffffef9df93c647c4f16d1155eb96f7a1679 +size 134771 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Invalid-symbols.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Invalid-symbols.png new file mode 100644 index 0000000000..e0240e827d --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.Invalid-symbols.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2cc05f40aad619c43b543c947e557174964cd860fcfa80257fa766aa88ebfad +size 143415 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.No-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.No-alias.png new file mode 100644 index 0000000000..31d81148b1 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.No-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1930d51d5f0634aa06250abe7740029b3d8fc2f3ba319653e54aaf3bacccfb +size 111971 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.With-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.With-alias.png new file mode 100644 index 0000000000..f206d34850 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPad-pseudo.With-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3552554ad8eb6998cd13a1b329d1e823563e18f04de4afff9b2b2fee4275a0a +size 111972 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Already-existing.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Already-existing.png new file mode 100644 index 0000000000..e8dec6419f --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Already-existing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1298b7f77d89e0a0409b56636d36ccdcc38c765fb709b19031d8eb6d7cccd1e +size 70278 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Invalid-symbols.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Invalid-symbols.png new file mode 100644 index 0000000000..bda4524df5 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.Invalid-symbols.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60ecce7bb6772f0693322b77b88125f7010bf8236a3f04747e856ad03e46e65a +size 73972 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.No-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.No-alias.png new file mode 100644 index 0000000000..79d3666838 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.No-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8bb258d0a65f5ab6bd36ce68909417e220b7336bac504ed9ba1c9ca77e3dc8b +size 54967 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.With-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.With-alias.png new file mode 100644 index 0000000000..2a901a3955 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-en-GB.With-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3231612e474ca5ed2a5681162fecabada27d3407b28be708c7f43d9efa80aaa7 +size 54754 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Already-existing.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Already-existing.png new file mode 100644 index 0000000000..ddfe3ccf1a --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Already-existing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bd8f03a48240719376911cfcbb0b972e7f6295c0d052b93d33948606e88ce0b +size 93574 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Invalid-symbols.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Invalid-symbols.png new file mode 100644 index 0000000000..0f04668702 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.Invalid-symbols.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a350ef87ce4dbce1b2020bed6932aee53fe34e118e72045f68d9a857879ed135 +size 101676 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.No-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.No-alias.png new file mode 100644 index 0000000000..0d840eff21 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.No-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7dc96671de7a8e204694533122addb9a64f705bf1818d6a0f46a1ba535571a1e +size 68334 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.With-alias.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.With-alias.png new file mode 100644 index 0000000000..e379f7e35d --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_editRoomAddressScreen-iPhone-16-pseudo.With-alias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcd12e17832a79ab7a4f7bbcb7973e1ab267e2a0d40b850b5dac6b342bdf4eb6 +size 68151 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Private-invite-only-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Private-invite-only-room.png new file mode 100644 index 0000000000..723cf7cd0c --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Private-invite-only-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0f8cd3542eb8dd9c7dbb553d37a154cd0cc932333e31ad2e69a37b2b8acacf7 +size 163760 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room-without-address.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room-without-address.png new file mode 100644 index 0000000000..9878a63129 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room-without-address.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4396683e4cab8545f4b66c415d45d1715cb6ff1bb0cb50eb99550c8b33f9fca1 +size 189113 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room.png new file mode 100644 index 0000000000..282cc5b9e5 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Public-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc13f5619bcf5ded950861338375147fce1859e992680de40b279b805a3ae987 +size 217894 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Restricted-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Restricted-room.png new file mode 100644 index 0000000000..7be4e81e0b --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-en-GB.Restricted-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:527832138f736cd387b754335932b59c5e34625fe26a2f2d24780cc153ff526b +size 217589 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Private-invite-only-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Private-invite-only-room.png new file mode 100644 index 0000000000..8bad2dba75 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Private-invite-only-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19e542bbd4a2cb27b587e90432a4499ee2cad27d4a859139eefaee2a09165c69 +size 192471 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room-without-address.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room-without-address.png new file mode 100644 index 0000000000..9354d772d0 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room-without-address.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65f2a4d8f484853aec99db22f3edd79d3aeb22b86c5c99f22545b1dbe0d997c0 +size 239270 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room.png new file mode 100644 index 0000000000..e4e59bd1e8 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Public-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5b37bec8fcde0df53e345db40f55148854cdfa4be37cf79409abf498b39f368 +size 266058 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Restricted-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Restricted-room.png new file mode 100644 index 0000000000..1106bf65c7 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPad-pseudo.Restricted-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:892a0fcb4bb2cf99d025ac954ed6abaf162aa3f172a6c0b690bbe8b647940248 +size 264851 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Private-invite-only-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Private-invite-only-room.png new file mode 100644 index 0000000000..f8d9bf4534 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Private-invite-only-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a88826a30f9541308c0cdab66add64b240a62271515455054a347695c3aa2cc4 +size 108223 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room-without-address.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room-without-address.png new file mode 100644 index 0000000000..78e0b49c97 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room-without-address.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e1d4a3efbbcc9a95eb8bdade3f4a94820d2dff3eac58939ef1c001915f10c52 +size 134514 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room.png new file mode 100644 index 0000000000..c4d5fd6eca --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Public-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b5d2893837ef5fef4b805162a67e951a796f0f83c393d871ef6ab18a099403a +size 146429 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Restricted-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Restricted-room.png new file mode 100644 index 0000000000..968af8eb2b --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-en-GB.Restricted-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9603ac222e27850a359500794e23a4398c5bdd37b4965c8eae18aae1dac07b81 +size 146037 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Private-invite-only-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Private-invite-only-room.png new file mode 100644 index 0000000000..81d3a21db0 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Private-invite-only-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68bb453000d5cc1dafeab24769c6b362756b157f4432ab4037c39a4fd10383ea +size 157801 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room-without-address.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room-without-address.png new file mode 100644 index 0000000000..b5786f478c --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room-without-address.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d0d71cd5103ff844500ec7f3610214940628827dc8edab434f9c22e46dd929a +size 169589 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room.png new file mode 100644 index 0000000000..235a1fd029 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Public-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9326d4ef2ae49c1650a2fa842782b00451456c65de98231546c6273617777b56 +size 183485 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Restricted-room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Restricted-room.png new file mode 100644 index 0000000000..ddfcaa9a82 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_securityAndPrivacyScreen-iPhone-16-pseudo.Restricted-room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f79a51e74d4df02fce286a31c9c31174b12a6b9f5e846b1e0beaf02b1d06535b +size 184789 diff --git a/UnitTests/Sources/EditRoomAddressScreenViewModelTests.swift b/UnitTests/Sources/EditRoomAddressScreenViewModelTests.swift new file mode 100644 index 0000000000..40129a5465 --- /dev/null +++ b/UnitTests/Sources/EditRoomAddressScreenViewModelTests.swift @@ -0,0 +1,21 @@ +// +// 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 EditRoomAddressScreenViewModelTests: XCTestCase { + var viewModel: EditRoomAddressScreenViewModelProtocol! + + var context: EditRoomAddressScreenViewModelType.Context { + viewModel.context + } + + override func setUpWithError() throws { } +} diff --git a/project.yml b/project.yml index c3fa4a9da7..347318a75a 100644 --- a/project.yml +++ b/project.yml @@ -61,7 +61,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 25.01.10-2 + exactVersion: 25.01.13 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios