From 350c04b0f312621df0684618755dc021bb3f9190 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:52:46 +0100 Subject: [PATCH] Add support for homeserver capabilities to disable the `UserDetailsEditScreen` UI. (#5421) Add support for homeserver capabilities to disable editing your user profile. Also updates editable avatar size/formatting to match the latest Figma at the same time. --- ElementX.xcodeproj/project.pbxproj | 20 ++ ElementX/Sources/Mocks/ClientProxyMock.swift | 8 + .../Mocks/Generated/GeneratedMocks.swift | 171 ++++++++++++++++++ ElementX/Sources/Other/Avatars.swift | 6 +- .../SwiftUI/Views/EditAvatarButtonStyle.swift | 69 +++++++ .../View/CreateRoomScreen.swift | 24 +-- .../View/RoomDetailsEditScreen.swift | 78 ++++---- .../UserDetailsEditScreenModels.swift | 3 + .../UserDetailsEditScreenViewModel.swift | 2 + .../View/UserDetailsEditScreen.swift | 36 ++-- .../HomeserverCapabilitiesProxy.swift | 43 +++++ .../HomeserverCapabilitiesProxyProtocol.swift | 15 ++ .../Sources/Services/Client/ClientProxy.swift | 35 ++-- .../Services/Client/ClientProxyProtocol.swift | 2 + ...oom.Create-Room-with-avatar-iPad-en-GB.png | 4 +- ...om.Create-Room-with-avatar-iPad-pseudo.png | 4 +- ...m.Create-Room-with-avatar-iPhone-en-GB.png | 4 +- ....Create-Room-with-avatar-iPhone-pseudo.png | 4 +- ...om.Create-Space-with-avatar-iPad-en-GB.png | 4 +- ...m.Create-Space-with-avatar-iPad-pseudo.png | 4 +- ....Create-Space-with-avatar-iPhone-en-GB.png | 4 +- ...Create-Space-with-avatar-iPhone-pseudo.png | 4 +- ...mDetailsEditScreen.Editable-iPad-en-GB.png | 4 +- ...DetailsEditScreen.Editable-iPad-pseudo.png | 4 +- ...etailsEditScreen.Editable-iPhone-en-GB.png | 4 +- ...tailsEditScreen.Editable-iPhone-pseudo.png | 4 +- ...DetailsEditScreen.Read-only-iPad-en-GB.png | 4 +- ...etailsEditScreen.Read-only-iPad-pseudo.png | 4 +- ...tailsEditScreen.Read-only-iPhone-en-GB.png | 4 +- ...ailsEditScreen.Read-only-iPhone-pseudo.png | 4 +- ...roomDetailsEditScreen.Space-iPad-en-GB.png | 3 + ...oomDetailsEditScreen.Space-iPad-pseudo.png | 3 + ...omDetailsEditScreen.Space-iPhone-en-GB.png | 3 + ...mDetailsEditScreen.Space-iPhone-pseudo.png | 3 + ...erDetailsEditScreen.Default-iPad-en-GB.png | 3 + ...rDetailsEditScreen.Default-iPad-pseudo.png | 3 + ...DetailsEditScreen.Default-iPhone-en-GB.png | 3 + ...etailsEditScreen.Default-iPhone-pseudo.png | 3 + ...DetailsEditScreen.Read-Only-iPad-en-GB.png | 3 + ...etailsEditScreen.Read-Only-iPad-pseudo.png | 3 + ...tailsEditScreen.Read-Only-iPhone-en-GB.png | 3 + ...ailsEditScreen.Read-Only-iPhone-pseudo.png | 3 + .../userDetailsEditScreen.iPad-en-GB-0.png | 3 - .../userDetailsEditScreen.iPad-pseudo-0.png | 3 - .../userDetailsEditScreen.iPhone-en-GB-0.png | 3 - .../userDetailsEditScreen.iPhone-pseudo-0.png | 3 - 46 files changed, 482 insertions(+), 142 deletions(-) create mode 100644 ElementX/Sources/Other/SwiftUI/Views/EditAvatarButtonStyle.swift create mode 100644 ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxy.swift create mode 100644 ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxyProtocol.swift create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-pseudo.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-en-GB-0.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-pseudo-0.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-en-GB-0.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-pseudo-0.png diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 16ee3d1c6..b909d6875 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -905,6 +905,7 @@ 9707AF8D41667FA9B35E8953 /* UserToInvite.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED25719E19B205B668FDACFF /* UserToInvite.swift */; }; 97189E495F0E47805D1868DB /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; }; 973C48F9E4EFB808F61BE401 /* LocationRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */; }; + 97550ECE8A1D28FEF74FBACC /* HomeserverCapabilitiesProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E8591F06B2DAA284181308 /* HomeserverCapabilitiesProxyProtocol.swift */; }; 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */; }; 97969EF0B9C412CD38E5CA93 /* AppLockScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4005D82E9D27BAF006A8FE1 /* AppLockScreenViewModel.swift */; }; 97BAEDD9054FB5F233EE928B /* EncryptionResetScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306AB507E1027D6C5C147EB6 /* EncryptionResetScreenModels.swift */; }; @@ -1132,6 +1133,7 @@ C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99E13633862847D8B7E2815 /* StartChatScreenModels.swift */; }; C08AAE7563E0722C9383F51C /* RoomMembersListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */; }; C097D5453640E27D397943CB /* TargetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D829FD8958376614504B18 /* TargetConfiguration.swift */; }; + C0A845AAFEB7E1BEAE04306E /* HomeserverCapabilitiesProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 751C3DC87817362097287DCC /* HomeserverCapabilitiesProxy.swift */; }; C0B97FFEC0083F3A36609E61 /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A243A6E6207297123E60DE48 /* TimelineItemMacContextMenu.swift */; }; C11939FDC40716C4387275A4 /* NotificationSettingsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */; }; C11D4A49DC29D89CE2BB31B8 /* MediaEventsTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976ED77B772F50C4BAD757E7 /* MediaEventsTimelineScreenViewModel.swift */; }; @@ -1245,6 +1247,7 @@ D2466C6BC8CAD8FADD7BF89B /* RoomPreviewProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6695C64F066628411EAD21E9 /* RoomPreviewProxyMock.swift */; }; D26093BB80B69092B0E9AC7C /* PinnedItemsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */; }; D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */; }; + D293B8277CF50611C284CAB5 /* EditAvatarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D7846847BA9D2C5346ED9C /* EditAvatarButtonStyle.swift */; }; D29E999538E5ABC00E1668F8 /* ElementNavigationStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */; }; D2CBC380FEBCBF29263B8446 /* AudioPlaybackSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B4E56DBFFD4A7A39D10F5 /* AudioPlaybackSpeed.swift */; }; D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; }; @@ -2250,6 +2253,7 @@ 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelProtocol.swift; sourceTree = ""; }; 74FCAA90142DFBFA1E3E4216 /* SpaceAddRoomsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceAddRoomsScreenCoordinator.swift; sourceTree = ""; }; 7509AB72755DCC4B4E721B36 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/SAS.strings; sourceTree = ""; }; + 751C3DC87817362097287DCC /* HomeserverCapabilitiesProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeserverCapabilitiesProxy.swift; sourceTree = ""; }; 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = ""; }; 753B4C6C0EDDCBF0708DC384 /* TimelineItemSendInfoLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSendInfoLabel.swift; sourceTree = ""; }; 75B3CE05643C7791D46AC54B /* LeaveSpaceHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceHandleProxy.swift; sourceTree = ""; }; @@ -2350,6 +2354,7 @@ 858F8D0B0D51CC41BAA18E24 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 859F51637DA710BBE7B70D6D /* ChatsSpaceFiltersScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsSpaceFiltersScreenViewModel.swift; sourceTree = ""; }; 85A1941B874A3BE9CDDF43EF /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; }; + 85E8591F06B2DAA284181308 /* HomeserverCapabilitiesProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeserverCapabilitiesProxyProtocol.swift; sourceTree = ""; }; 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = ""; }; 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 86376BEE425704AEE197CA54 /* PillContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContext.swift; sourceTree = ""; }; @@ -2372,6 +2377,7 @@ 897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = ""; }; 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreen.swift; sourceTree = ""; }; 89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetTests.swift; sourceTree = ""; }; + 89D7846847BA9D2C5346ED9C /* EditAvatarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAvatarButtonStyle.swift; sourceTree = ""; }; 89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormButtonStyles.swift; sourceTree = ""; }; 8A1F2AAA3F0F2B72D2FFE4D0 /* MapTilerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerConfiguration.swift; sourceTree = ""; }; 8A8DCBD0ABAADFDE5AF17E1F /* LiveLocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationRoomTimelineItem.swift; sourceTree = ""; }; @@ -3244,6 +3250,7 @@ FCE7249621F507F34A8122FB /* Audio */, AAFDD509929A0CCF8BCE51EB /* Authentication */, 0ED3F5C21537519389C07644 /* BugReport */, + 786C421000CA16305B0FC55C /* Capabilities */, 8039515BAA53B7C3275AC64A /* Client */, 8B5E91450E85A9689931B221 /* ComposerDraft */, 92E99C57D7F92ED16F73282C /* ElementCall */, @@ -3807,6 +3814,7 @@ 07934EF08BB39353E4A94272 /* BlurEffectView.swift */, FEC4B431B0117BDEE697DB4A /* ComposerDisabledView.swift */, B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */, + 89D7846847BA9D2C5346ED9C /* EditAvatarButtonStyle.swift */, E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */, 8F4F0AB250EFA7B71FB2BDB2 /* HorizontalHighlightGradient.swift */, 98ABC939BC8F08CA3E967D6C /* JoinCallButton.swift */, @@ -5063,6 +5071,15 @@ path = View; sourceTree = ""; }; + 786C421000CA16305B0FC55C /* Capabilities */ = { + isa = PBXGroup; + children = ( + 751C3DC87817362097287DCC /* HomeserverCapabilitiesProxy.swift */, + 85E8591F06B2DAA284181308 /* HomeserverCapabilitiesProxyProtocol.swift */, + ); + path = Capabilities; + sourceTree = ""; + }; 78915D878159D302395D57BF /* SupportingFiles */ = { isa = PBXGroup; children = ( @@ -8230,6 +8247,7 @@ 037006FB6DF1374F94E4058D /* Dictionary.swift in Sources */, EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */, 4FDC8A9764CFDA90CE035725 /* Duration.swift in Sources */, + D293B8277CF50611C284CAB5 /* EditAvatarButtonStyle.swift in Sources */, 4B25CDB4AA2C2AC0B4577217 /* EditRoomAddressListRow.swift in Sources */, 4764FC9A843D1F9865EDC29C /* EditRoomAddressScreen.swift in Sources */, 2BC579CB5CE90CFE07CA0955 /* EditRoomAddressScreenCoordinator.swift in Sources */, @@ -8322,6 +8340,8 @@ A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */, DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */, 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */, + C0A845AAFEB7E1BEAE04306E /* HomeserverCapabilitiesProxy.swift in Sources */, + 97550ECE8A1D28FEF74FBACC /* HomeserverCapabilitiesProxyProtocol.swift in Sources */, 277FD4394EAFF323DA34997E /* HorizontalHighlightGradient.swift in Sources */, 2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */, C3BB6887CF13B19182E81F87 /* IdentityConfirmationScreenCoordinator.swift in Sources */, diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 372c67339..c6eb56b68 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -29,6 +29,9 @@ struct ClientProxyMockConfiguration { var timelineMediaVisibility = TimelineMediaVisibility.always var hideInviteAvatars = false + var canChangeAvatar = true + var canChangeDisplayName = true + var maxMediaUploadSize: UInt = 100 * 1024 * 1024 class Overrides { @@ -109,6 +112,11 @@ extension ClientProxyMock { spaceService = SpaceServiceProxyMock(configuration.spaceServiceConfiguration) linkNewDeviceServiceReturnValue = LinkNewDeviceServiceMock(.init()) + let capabilities = HomeserverCapabilitiesProxyMock() + capabilities.canChangeAvatarReturnValue = configuration.canChangeAvatar + capabilities.canChangeDisplayNameReturnValue = configuration.canChangeDisplayName + self.capabilities = capabilities + roomForIdentifierClosure = { [weak self] identifier in if let room = self?.roomSummaryProvider.roomListPublisher.value.first(where: { $0.id == identifier }) { let joinedRoomIDs = configuration.overrides.joinedRoomIDs diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 0ec09c9b6..468cbd39e 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2696,6 +2696,11 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { set(value) { underlyingSpaceService = value } } var underlyingSpaceService: SpaceServiceProxyProtocol! + var capabilities: HomeserverCapabilitiesProxyProtocol { + get { return underlyingCapabilities } + set(value) { underlyingCapabilities = value } + } + var underlyingCapabilities: HomeserverCapabilitiesProxyProtocol! var isReportRoomSupportedCallsCount = 0 var isReportRoomSupportedCalled: Bool { return isReportRoomSupportedCallsCount > 0 @@ -6945,6 +6950,172 @@ class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol, @unchecked S } } } +class HomeserverCapabilitiesProxyMock: HomeserverCapabilitiesProxyProtocol, @unchecked Sendable { + + //MARK: - refresh + + var refreshUnderlyingCallsCount = 0 + var refreshCallsCount: Int { + get { + if Thread.isMainThread { + return refreshUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = refreshUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + refreshUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + refreshUnderlyingCallsCount = newValue + } + } + } + } + var refreshCalled: Bool { + return refreshCallsCount > 0 + } + var refreshClosure: (() async -> Void)? + + func refresh() async { + refreshCallsCount += 1 + await refreshClosure?() + } + //MARK: - canChangeAvatar + + var canChangeAvatarUnderlyingCallsCount = 0 + var canChangeAvatarCallsCount: Int { + get { + if Thread.isMainThread { + return canChangeAvatarUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = canChangeAvatarUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + canChangeAvatarUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + canChangeAvatarUnderlyingCallsCount = newValue + } + } + } + } + var canChangeAvatarCalled: Bool { + return canChangeAvatarCallsCount > 0 + } + + var canChangeAvatarUnderlyingReturnValue: Bool! + var canChangeAvatarReturnValue: Bool! { + get { + if Thread.isMainThread { + return canChangeAvatarUnderlyingReturnValue + } else { + var returnValue: Bool? = nil + DispatchQueue.main.sync { + returnValue = canChangeAvatarUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + canChangeAvatarUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + canChangeAvatarUnderlyingReturnValue = newValue + } + } + } + } + var canChangeAvatarClosure: (() async -> Bool)? + + func canChangeAvatar() async -> Bool { + canChangeAvatarCallsCount += 1 + if let canChangeAvatarClosure = canChangeAvatarClosure { + return await canChangeAvatarClosure() + } else { + return canChangeAvatarReturnValue + } + } + //MARK: - canChangeDisplayName + + var canChangeDisplayNameUnderlyingCallsCount = 0 + var canChangeDisplayNameCallsCount: Int { + get { + if Thread.isMainThread { + return canChangeDisplayNameUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = canChangeDisplayNameUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + canChangeDisplayNameUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + canChangeDisplayNameUnderlyingCallsCount = newValue + } + } + } + } + var canChangeDisplayNameCalled: Bool { + return canChangeDisplayNameCallsCount > 0 + } + + var canChangeDisplayNameUnderlyingReturnValue: Bool! + var canChangeDisplayNameReturnValue: Bool! { + get { + if Thread.isMainThread { + return canChangeDisplayNameUnderlyingReturnValue + } else { + var returnValue: Bool? = nil + DispatchQueue.main.sync { + returnValue = canChangeDisplayNameUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + canChangeDisplayNameUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + canChangeDisplayNameUnderlyingReturnValue = newValue + } + } + } + } + var canChangeDisplayNameClosure: (() async -> Bool)? + + func canChangeDisplayName() async -> Bool { + canChangeDisplayNameCallsCount += 1 + if let canChangeDisplayNameClosure = canChangeDisplayNameClosure { + return await canChangeDisplayNameClosure() + } else { + return canChangeDisplayNameReturnValue + } + } +} class InvitedRoomProxyMock: InvitedRoomProxyProtocol, @unchecked Sendable { var info: BaseRoomInfoProxyProtocol { get { return underlyingInfo } diff --git a/ElementX/Sources/Other/Avatars.swift b/ElementX/Sources/Other/Avatars.swift index a368c184f..06f91d4aa 100644 --- a/ElementX/Sources/Other/Avatars.swift +++ b/ElementX/Sources/Other/Avatars.swift @@ -134,10 +134,12 @@ enum RoomAvatarSizeOnScreen { case globalSearch case roomSelection case details + case editRoomDetails case notificationSettings case roomDirectorySearch case joinRoom case spaceHeader + case editSpaceDetails case spaceAddRooms case spaceAddRoomsSelected case completionSuggestions @@ -157,9 +159,9 @@ enum RoomAvatarSizeOnScreen { case .chats, .spaces, .spaceSettings, .spaceAddRoomsSelected: 52 - case .joinRoom, .spaceHeader: + case .joinRoom, .spaceHeader, .editSpaceDetails: 64 - case .details: + case .details, .editRoomDetails: 96 } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/EditAvatarButtonStyle.swift b/ElementX/Sources/Other/SwiftUI/Views/EditAvatarButtonStyle.swift new file mode 100644 index 000000000..39018b906 --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/EditAvatarButtonStyle.swift @@ -0,0 +1,69 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Compound +import SwiftUI + +struct EditAvatarButtonStyle: ButtonStyle { + @Environment(\.isEnabled) private var isEnabled + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .overlay(alignment: .bottomTrailing) { + if isEnabled { + EditAvatarBadge() + .scaledOffset(x: 8, y: 0, relativeTo: .title) + } + } + } +} + +struct EditAvatarBadge: View { + var body: some View { + CompoundIcon(\.edit, size: .small, relativeTo: .body) + .foregroundStyle(.compound.iconPrimary) + .scaledPadding(5, relativeTo: .title) + .background { + Circle() + .fill(Color.compound.bgCanvasDefault) + .overlay { + Circle() + .inset(by: -0.5) + .stroke(.compound.borderInteractiveSecondary, lineWidth: 1) + } + } + .scaledPadding(4, relativeTo: .title) + .background(.compound.bgSubtleSecondaryLevel0, in: Circle()) + .scaledPadding(-4) + .accessibilityHidden(true) + } +} + +struct EditAvatarButtonStyle_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + Button { } label: { + LoadableAvatarImage(url: nil, + name: "Test", contentID: "test", + avatarSize: .user(on: .editUserDetails), + mediaProvider: MediaProviderMock(configuration: .init())) + } + .buttonStyle(EditAvatarButtonStyle()) + + Button { } label: { + LoadableAvatarImage(url: nil, + name: "Test", contentID: "test", + avatarSize: .user(on: .editUserDetails), + mediaProvider: MediaProviderMock(configuration: .init())) + } + .buttonStyle(EditAvatarButtonStyle()) + .disabled(true) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(.compound.bgSubtleSecondaryLevel0) + } +} diff --git a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift index b2636d48d..a3a451ea9 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift @@ -114,11 +114,10 @@ struct CreateRoomScreen: View { .resizable() .aspectRatio(contentMode: .fill) .scaledFrame(size: 70, relativeTo: .title) - .clipShape(context.viewState.isSpace ? AnyShape(RoundedRectangle(cornerRadius: 16)) : AnyShape(Circle())) + .avatarShape(context.viewState.isSpace ? .roundedRect : .circle, size: 70) .overlay(alignment: .bottomTrailing) { - editAvatarBadge - .scaledOffset(x: 12, y: 4, relativeTo: .title) - .accessibilityHidden(true) + EditAvatarBadge() + .scaledOffset(x: 8, y: 0, relativeTo: .title) } } else { CompoundIcon(\.takePhoto, size: .medium, relativeTo: .title) @@ -153,23 +152,6 @@ struct CreateRoomScreen: View { } } - private var editAvatarBadge: some View { - CompoundIcon(\.edit, size: .small, relativeTo: .body) - .foregroundStyle(.compound.iconPrimary) - .scaledPadding(5, relativeTo: .title) - .background { - Circle() - .fill(Color.compound.bgCanvasDefault) - .overlay { - Circle() - .inset(by: 0.5) - .stroke(.compound.borderInteractiveSecondary, lineWidth: 1) - } - } - .scaledPadding(3.5, relativeTo: .title) - .background(.compound.bgSubtleSecondaryLevel0, in: Circle()) - } - private var topicSection: some View { Section { ListRow(label: .plain(title: L10n.screenCreateRoomTopicPlaceholder), diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift index a1c6624c4..6c1aba165 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift @@ -11,11 +11,12 @@ import SwiftUI struct RoomDetailsEditScreen: View { @ObservedObject var context: RoomDetailsEditScreenViewModel.Context + + private enum Focus { case name, topic } @FocusState private var focus: Focus? - private enum Focus { - case name - case topic + private var isSpace: Bool { + context.viewState.isSpace } var body: some View { @@ -60,20 +61,15 @@ struct RoomDetailsEditScreen: View { url: context.viewState.avatarURL, name: context.viewState.initialName, contentID: context.viewState.roomID, - shape: context.viewState.isSpace ? .roundedRect : .circle, - avatarSize: .user(on: .memberDetails), + shape: isSpace ? .roundedRect : .circle, + avatarSize: .room(on: isSpace ? .editSpaceDetails : .editRoomDetails), mediaProvider: context.mediaProvider) .accessibilityLabel(L10n.a11yEditAvatar) - .overlay(alignment: .bottomTrailing) { - if context.viewState.canEditAvatar { - avatarOverlayIcon - } - } .confirmationDialog("", isPresented: $context.showMediaSheet) { mediaActionSheet } } - .buttonStyle(.plain) + .buttonStyle(EditAvatarButtonStyle()) .disabled(!context.viewState.canEditAvatar) .frame(maxWidth: .infinity, alignment: .center) .listRowBackground(Color.clear) @@ -100,7 +96,7 @@ struct RoomDetailsEditScreen: View { private var topicSection: some View { Section { if context.viewState.canEditTopic { - ListRow(label: .plain(title: context.viewState.isSpace ? L10n.commonSpaceTopicPlaceholder : L10n.commonTopicPlaceholder), + ListRow(label: .plain(title: isSpace ? L10n.commonSpaceTopicPlaceholder : L10n.commonTopicPlaceholder), kind: .textField(text: $context.topic, axis: .vertical)) .focused($focus, equals: .topic) .lineLimit(3...) @@ -116,17 +112,6 @@ struct RoomDetailsEditScreen: View { } } - private var avatarOverlayIcon: some View { - CompoundIcon(\.editSolid, size: .xSmall, relativeTo: .compound.bodyLG) - .foregroundColor(.white) - .padding(4) - .background { - Circle() - .foregroundColor(.black) - } - .accessibilityHidden(true) - } - @ViewBuilder private var mediaActionSheet: some View { Button { @@ -152,27 +137,9 @@ struct RoomDetailsEditScreen: View { // MARK: - Previews struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { - static let editableViewModel = { - let roomProxy = JoinedRoomProxyMock(.init(id: "test_id", - name: "Room", - members: [.mockMeAdmin])) - - return RoomDetailsEditScreenViewModel(roomProxy: roomProxy, - userSession: UserSessionMock(.init()), - mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), - userIndicatorController: UserIndicatorControllerMock.default) - }() - - static let readOnlyViewModel = { - let roomProxy = JoinedRoomProxyMock(.init(id: "test_id", - name: "Room", - members: [.mockAlice])) - - return RoomDetailsEditScreenViewModel(roomProxy: roomProxy, - userSession: UserSessionMock(.init()), - mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), - userIndicatorController: UserIndicatorControllerMock.default) - }() + static let editableViewModel = makeViewModel(readOnly: false) + static let readOnlyViewModel = makeViewModel(readOnly: true) + static let editableSpaceViewModel = makeViewModel(readOnly: false, isSpace: true) static var previews: some View { ElementNavigationStack { @@ -183,9 +150,26 @@ struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { ElementNavigationStack { RoomDetailsEditScreen(context: editableViewModel.context) } - .snapshotPreferences(expect: editableViewModel.context.$viewState.map { state in - state.canEditTopic == true - }) + .snapshotPreferences(expect: editableViewModel.context.$viewState.map { $0.canEditTopic == true }) .previewDisplayName("Editable") + + ElementNavigationStack { + RoomDetailsEditScreen(context: editableSpaceViewModel.context) + } + .snapshotPreferences(expect: editableSpaceViewModel.context.$viewState.map { $0.canEditTopic == true }) + .previewDisplayName("Space") + } + + static func makeViewModel(readOnly: Bool, isSpace: Bool = false) -> RoomDetailsEditScreenViewModel { + let members: [RoomMemberProxyMock] = readOnly ? [.mockAlice] : [.mockMeAdmin] + let roomProxy = JoinedRoomProxyMock(.init(id: "test_id", + name: isSpace ? "Space" : "Room", + isSpace: isSpace, + members: members)) + + return RoomDetailsEditScreenViewModel(roomProxy: roomProxy, + userSession: UserSessionMock(.init()), + mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), + userIndicatorController: UserIndicatorControllerMock.default) } } diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift index 941222f2b..40922c05f 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift @@ -18,6 +18,9 @@ enum UserDetailsEditScreenViewModelAction { struct UserDetailsEditScreenViewState: BindableState { let userID: String + var canEditAvatar = true + var canEditDisplayName = true + var currentAvatarURL: URL? var selectedAvatarURL: URL? diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift index 9abc722ad..c74c07e36 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift @@ -58,6 +58,8 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe Task { await self.clientProxy.loadUserAvatarURL() await self.clientProxy.loadUserDisplayName() + state.canEditAvatar = await clientProxy.capabilities.canChangeAvatar() + state.canEditDisplayName = await clientProxy.capabilities.canChangeDisplayName() } } diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift index 935e6c7d8..60de80a9e 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift @@ -67,14 +67,12 @@ struct UserDetailsEditScreen: View { shape: .circle, avatarSize: .user(on: .editUserDetails), mediaProvider: context.mediaProvider) - .overlay(alignment: .bottomTrailing) { - avatarOverlayIcon - } .confirmationDialog("", isPresented: $context.showMediaSheet) { mediaActionSheet } } - .buttonStyle(.plain) + .buttonStyle(EditAvatarButtonStyle()) + .disabled(!context.viewState.canEditAvatar) .frame(maxWidth: .infinity, alignment: .center) .listRowBackground(Color.clear) } @@ -84,22 +82,13 @@ struct UserDetailsEditScreen: View { ListRow(label: .plain(title: L10n.screenEditProfileDisplayNamePlaceholder), kind: .textField(text: $context.name, axis: .horizontal)) .focused($focus) + .disabled(!context.viewState.canEditDisplayName) } header: { Text(L10n.screenEditProfileDisplayName) .compoundListSectionHeader() } } - private var avatarOverlayIcon: some View { - CompoundIcon(\.editSolid, size: .xSmall, relativeTo: .compound.bodyLG) - .foregroundColor(.white) - .padding(4) - .background { - Circle() - .foregroundColor(.black) - } - } - @ViewBuilder private var mediaActionSheet: some View { Button { @@ -126,13 +115,26 @@ struct UserDetailsEditScreen: View { // MARK: - Previews struct UserDetailsEditScreen_Previews: PreviewProvider, TestablePreview { - static let viewModel = UserDetailsEditScreenViewModel(userSession: UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@stefan:matrix.org")))), - mediaUploadingPreprocessor: .init(appSettings: ServiceLocator.shared.settings), - userIndicatorController: UserIndicatorControllerMock.default) + static let viewModel = makeViewModel() + static let readOnlyViewModel = makeViewModel(canChangeProfile: false) static var previews: some View { ElementNavigationStack { UserDetailsEditScreen(context: viewModel.context) } + .previewDisplayName("Default") + + ElementNavigationStack { + UserDetailsEditScreen(context: readOnlyViewModel.context) + } + .previewDisplayName("Read Only") + } + + static func makeViewModel(canChangeProfile: Bool = true) -> UserDetailsEditScreenViewModel { + UserDetailsEditScreenViewModel(userSession: UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@stefan:matrix.org", + canChangeAvatar: canChangeProfile, + canChangeDisplayName: canChangeProfile)))), + mediaUploadingPreprocessor: .init(appSettings: ServiceLocator.shared.settings), + userIndicatorController: UserIndicatorControllerMock.default) } } diff --git a/ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxy.swift b/ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxy.swift new file mode 100644 index 000000000..77973df46 --- /dev/null +++ b/ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxy.swift @@ -0,0 +1,43 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Foundation +import MatrixRustSDK + +struct HomeserverCapabilitiesProxy: HomeserverCapabilitiesProxyProtocol { + private let underlyingCapabilities: HomeserverCapabilitiesProtocol + + init(underlyingCapabilities: HomeserverCapabilitiesProtocol) { + self.underlyingCapabilities = underlyingCapabilities + } + + func refresh() async { + do { + try await underlyingCapabilities.refresh() + } catch { + MXLog.error("Failure refreshing homeserver capabilities: \(error)") + } + } + + func canChangeAvatar() async -> Bool { + do { + return try await underlyingCapabilities.canChangeAvatar() + } catch { + MXLog.error("Failure checking canChangeAvatar: \(error)") + return true + } + } + + func canChangeDisplayName() async -> Bool { + do { + return try await underlyingCapabilities.canChangeDisplayname() + } catch { + MXLog.error("Failure checking canChangeDisplayName: \(error)") + return true + } + } +} diff --git a/ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxyProtocol.swift b/ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxyProtocol.swift new file mode 100644 index 000000000..b82e653cf --- /dev/null +++ b/ElementX/Sources/Services/Capabilities/HomeserverCapabilitiesProxyProtocol.swift @@ -0,0 +1,15 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Foundation + +// sourcery: AutoMockable +protocol HomeserverCapabilitiesProxyProtocol { + func refresh() async + func canChangeAvatar() async -> Bool + func canChangeDisplayName() async -> Bool +} diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index d0fd9111a..7cbb463c1 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -64,6 +64,9 @@ class ClientProxy: ClientProxyProtocol { let spaceService: SpaceServiceProxyProtocol + let capabilities: HomeserverCapabilitiesProxyProtocol + private var capabilitiesRefreshTask: Task? + let eventStringBuilder: RoomEventStringBuilder private static var roomCreationPowerLevelOverrides: PowerLevels { @@ -208,6 +211,8 @@ class ClientProxy: ClientProxyProtocol { spaceService = await SpaceServiceProxy(spaceService: client.spaceService()) + capabilities = HomeserverCapabilitiesProxy(underlyingCapabilities: client.homeserverCapabilities()) + let configuredAppService = try await ClientProxyServices(client: client, actionsSubject: actionsSubject, notificationSettings: notificationSettings, @@ -1122,17 +1127,25 @@ class ClientProxy: ClientProxyProtocol { MXLog.info("Received room list update: \(state)") - guard state != .error, - state != .terminated else { - // The sync service is responsible of handling error and termination - return - } - - // Hide the sync spinner as soon as we get any update back - actionsSubject.send(.receivedSyncUpdate) - - if ignoredUsersSubject.value == nil { - updateIgnoredUsers() + switch state { + case .initial, .settingUp, .recovering: + break // Don't do anything until we're actually running. + case .running: + // Hide the sync spinner as soon as we get any update back + actionsSubject.send(.receivedSyncUpdate) + + if ignoredUsersSubject.value == nil { + updateIgnoredUsers() + } + + if capabilitiesRefreshTask == nil { + capabilitiesRefreshTask = Task { [weak self] in + await self?.capabilities.refresh() + self?.capabilitiesRefreshTask = nil + } + } + case .error, .terminated: + break // The sync service is responsible for handling error and termination } }) } diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 904637c12..eab45ffc1 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -145,6 +145,8 @@ protocol ClientProxyProtocol: AnyObject { var spaceService: SpaceServiceProxyProtocol { get } + var capabilities: HomeserverCapabilitiesProxyProtocol { get } + var isReportRoomSupported: Bool { get async } var isLiveKitRTCSupported: Bool { get async } diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-en-GB.png index 935dcd802..3770b5a68 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d8badd2dd08312b2e04f4a2ec0980e4d451739e994b2135397cb39d286f6df3 -size 164266 +oid sha256:f96b5709f029227211fc38ae9d8f864a379619caca0862fa9ec0d618dcdb2c87 +size 163998 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-pseudo.png index df7ae85fb..72941987a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c608fd1aa49c2b8889e4e656fca0a2d854a30c24a8028397d340f5bdfadff466 -size 186219 +oid sha256:cc2cc853cd1fe04db02544e9fcff375ff16d67f386e4cef54c51b5abe0638f60 +size 186061 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-en-GB.png index 90e8de5ae..568afa1d4 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba424bba91c740ab31c3b932edbfff630436d78328a068047a8c5bd9a25bf4c8 -size 111949 +oid sha256:12f7c5e3b1b53222a4f8cd64fa7bb3b6d0693857900e5fc731cebef4c67c6d43 +size 111616 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-pseudo.png index 00db15877..50536510e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-with-avatar-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e33c209512f8651725a93d596ade156df2d5f7c97038996de4bbb12b723f4e1e -size 130775 +oid sha256:292be3d2424c93b5f075748551d11eb602bd34063394fa2633628c3d010a449a +size 130474 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-en-GB.png index c7f9b8865..d1cab68b6 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:715a7d4430435115d3a5547cba02cbbc39e53bb6b3ae7cece245c7ff5930eb25 -size 157890 +oid sha256:6688f6f768a79e10416739f92728138560757eaa8ebc073a46fd77fe4f88bcf9 +size 158414 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-pseudo.png index c869edd3f..0fba98927 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1082ce0437aebd4f2ef37d1015dc8aecc1c9b00e680d9b51a8dda0973654e475 -size 175400 +oid sha256:3c3ef21335a1ba514dee7c40d3fc418f8514584d258319d5809093bffba9dd3e +size 176340 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-en-GB.png index 50c32e847..66cdbe690 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47f9a963891948d89163fad6621c6bccaccd83c05e727e1029510e41b3aefa3f -size 106477 +oid sha256:10a8e9bc5ba5ed5380d99fc4ba9bc4fa75599bc5b2d015e3a3d8910982a1265d +size 107405 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-pseudo.png index 6bde6ab9b..22738f271 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Space-with-avatar-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb08fcd0fa9d0417ce0ccabc59c944a6c93271a4fcb30e522299cd5029f65174 -size 123464 +oid sha256:236c2f150e3ec17220a40cfdf0274c2838f4d390a69009c6bdf934d91aec3a03 +size 124346 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-en-GB.png index 9f3fa20b9..e7c7400fe 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f82929e08fd6de3bcf0fc6dee6b5d9002cf257dea39154f4c390d271062708a -size 105800 +oid sha256:c3c491d9958bdc80618ecdf132bfa3ee8dbc9b196d19325237fb20b88356a159 +size 107256 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-pseudo.png index eab9eab3a..13d77aec4 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e010aad7b6a32216890867ecfb5ebf27e3874a048550a17ae643dac881b9e8 -size 114403 +oid sha256:2cdc317bbadb121afdb1ea2e80f3253cdb8e66b5c6a8eb9b24fc6cd9c6082682 +size 115846 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-en-GB.png index 445b03d91..03e8ac6a3 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24137b4c05c27b3f187c49751a8661e3fc48584f42c33f0cf11fdff0b27ec36e -size 58893 +oid sha256:b91094e03755f7e90aebc3cd5d50d4a32aed4f34fc67db081668ea554a79a4b0 +size 60105 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-pseudo.png index 129ce6576..2a2cc98f4 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Editable-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5ba17de537068bca19958113fe13914e40efb1fb988398a660cf0b2c4c52e29 -size 65014 +oid sha256:9c8b8d0fd5fb8d850f187b163f3b740f2f0ca031be5d821d31bc44c4d3a956b7 +size 66260 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-en-GB.png index e4444f07c..ad893c651 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb17df7d4e302d47a6f272c4084fa12ba3b23b72179ae6aed5950ffa751336e3 -size 95822 +oid sha256:62b1fc8176a793e990fcf4417c8daecf53323db7ec965c14b68bbf82852a8db8 +size 95986 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-pseudo.png index a686310c6..e4161cc83 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cb2e203539d455b4e2c9a2ba3f1323f438e6ee81afbadd53ee3379f7a32231d -size 100565 +oid sha256:cd8a44d0825329a7fbaac6d6008d744e4e2b17581f837cc2235eeede385b4e7d +size 100749 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-en-GB.png index 3d9e1216e..af3015a39 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b539bf77e066b1f77b6cc78aa7f4a7ab55ee00ca66dae552768a4af827839f9 -size 44850 +oid sha256:ddbbdb9901075b2387f257ac64f2fc3b5b7a66d8358a93bd7499b1247b8bc226 +size 44952 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-pseudo.png index 222aa4c07..1a26a6d04 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Read-only-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d74b49996c7b5a5e45b08ca8af7452e7db2d1039f6dae30031a6575c77c03f7 -size 45528 +oid sha256:0cbf5e0229996d3b8483338350f9fab528e9a9d9a75588bb59c3ea9fe796cc3b +size 45637 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-en-GB.png new file mode 100644 index 000000000..7ec0d69a7 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5660e7e8e2923dece48caaba208eb7ece4237ad1aaccbca883ed28e3399fcf47 +size 109885 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-pseudo.png new file mode 100644 index 000000000..68d249091 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e9dd3991f0a06afd9668be98204ba73f1e535367f375901e23436d5d971b923 +size 117998 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-en-GB.png new file mode 100644 index 000000000..9d52b8442 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03fa5dc84f58f5de19be8543a81b468259033534d2179339368f65757f856c67 +size 61315 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-pseudo.png new file mode 100644 index 000000000..91e7bee4a --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomDetailsEditScreen.Space-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e19131dd759b248f4ebd381370ecd28264260aab9bb86885b40dac0ad72a367 +size 67916 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-en-GB.png new file mode 100644 index 000000000..7eaeedbec --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5935c47b199d14f01b5f79c8b0a2adb1122fc48b1790366af480516371e55d12 +size 104772 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-pseudo.png new file mode 100644 index 000000000..6ac1a5e4e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa21d6018574f3ed5c99e1b5f50d097c2aa3175cb41a46f0d03a4d303f644d14 +size 111214 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-en-GB.png new file mode 100644 index 000000000..4ff043fdc --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2be135c4d993374eba85f7c8035dea2d5318fe199627c4b59fc4af1a31daecb9 +size 54325 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-pseudo.png new file mode 100644 index 000000000..550787065 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Default-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:947c3a92b8017c04740212ddd7da98138623c6ca0396b235ec4055ac57d24647 +size 59340 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-en-GB.png new file mode 100644 index 000000000..7eaeedbec --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5935c47b199d14f01b5f79c8b0a2adb1122fc48b1790366af480516371e55d12 +size 104772 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-pseudo.png new file mode 100644 index 000000000..6ac1a5e4e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa21d6018574f3ed5c99e1b5f50d097c2aa3175cb41a46f0d03a4d303f644d14 +size 111214 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-en-GB.png new file mode 100644 index 000000000..4ff043fdc --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2be135c4d993374eba85f7c8035dea2d5318fe199627c4b59fc4af1a31daecb9 +size 54325 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-pseudo.png new file mode 100644 index 000000000..550787065 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.Read-Only-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:947c3a92b8017c04740212ddd7da98138623c6ca0396b235ec4055ac57d24647 +size 59340 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-en-GB-0.png deleted file mode 100644 index 9367ff2dc..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-en-GB-0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:87f751f1906937a5372763b148c3ec2af1f814a9bd9ed35e9df132c4b251d1b4 -size 103332 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-pseudo-0.png deleted file mode 100644 index b5e53c97d..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPad-pseudo-0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ba30cd0d1605c167647bb337f7c52bbbdf0520a7e29ec7923d8e3ecea356d1e -size 109826 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-en-GB-0.png deleted file mode 100644 index f50dfec71..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-en-GB-0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:17f3c9d0c4f04e69378dd5f467cb5b22493af959aef7668f7cab609b0d622362 -size 53189 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-pseudo-0.png deleted file mode 100644 index 78560504a..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/userDetailsEditScreen.iPhone-pseudo-0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e3618e299d568d46eed494e6cf079067876e9cd999c02d376117fc90b9da99db -size 58222