Room's details edit screen (#956)
* Add RoomDetailsEditScreen template * Add navigation to the edit screen * Delete template code * Start RoomDetailsEditScreen UI * Manage power levels in RoomDetailsScreen * Start RoomDetailsEditScreenViewModel logic * Inject initial room state * Add cancel action * Expose set name/topic APIs in RoomProxy * Refine RoomDetailsEditScreen UI * Add save logic * Add localisations * Fix avatar image * Update localisations * Add “Add topic” button * Add feature flag * Add dismiss on save logic * Reduce throttling * Improve form logic * Fix UT build errors * Add media sheet * Add media preprocessing * Add LoadableEditableAvatarImage * Add condition on delete image * Add avatar save button logic * Add remove avatar logic * Cleanup * Fix edit bug in DM * Add upload avatar * Add focus * Add RoomDetailsViewModel UTs * Fix button style * Add UTs * Add empty topic ui test * Fix iPad sheet presentation * Revert topic appearance in room’s details * Address PR comments * Add UI tests
This commit is contained in:
parent
524997438f
commit
f2b7faa183
|
@ -32,6 +32,7 @@
|
|||
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
|
||||
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */; };
|
||||
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
||||
0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; };
|
||||
0BE4D5CBF86956410F071F91 /* CreateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */; };
|
||||
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
|
||||
0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */; };
|
||||
|
@ -232,6 +233,7 @@
|
|||
651341E67C3514F9811A1EC1 /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */; };
|
||||
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1423AB065857FA546444DB15 /* NotificationManager.swift */; };
|
||||
6530865EB9A8C0F0AF0216DA /* ServerSelectionScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9501D11B4258DFA33BA3B40F /* ServerSelectionScreenModels.swift */; };
|
||||
659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; };
|
||||
65EDA77363BEDC40CDE43B43 /* InvitesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ADEA322D2089391E049535 /* InvitesScreen.swift */; };
|
||||
661A664C6EDF856B05519206 /* FilePreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F562E2CBA002E8E1B6545C38 /* FilePreviewScreen.swift */; };
|
||||
663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; };
|
||||
|
@ -297,6 +299,7 @@
|
|||
7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */; };
|
||||
7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */; };
|
||||
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
|
||||
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; };
|
||||
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
|
||||
80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; };
|
||||
8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */; };
|
||||
|
@ -386,6 +389,7 @@
|
|||
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
|
||||
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */; };
|
||||
9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
|
||||
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; };
|
||||
9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */; };
|
||||
9E838A62918E47BC72D6640D /* UserIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB54B4F94686CCF0289B72F /* UserIndicatorPresenter.swift */; };
|
||||
9F19096BFA629C0AC282B1E4 /* CreateRoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */; };
|
||||
|
@ -411,6 +415,7 @@
|
|||
A50849766F056FD1DB942DEA /* AlertInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */; };
|
||||
A5B9EF45C7B8ACEB4954AE36 /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9780389F8A53E4D26E23DD03 /* LoginScreenViewModelProtocol.swift */; };
|
||||
A5D551E5691749066E0E0C44 /* RoomDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */; };
|
||||
A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; };
|
||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
||||
A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */; };
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
|
@ -490,6 +495,7 @@
|
|||
C4078364FD9FA00EA9D00A15 /* RoomMembersListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CDF9A107BFE6C79B58D6B5 /* RoomMembersListScreenViewModelProtocol.swift */; };
|
||||
C413D36D44F89DE63D3ADFA4 /* ReportContentScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */; };
|
||||
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */; };
|
||||
C49FCC766673006B6D299F1C /* RoomDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */; };
|
||||
C4D2BCAA54E2C62B94B24AF4 /* InviteUsersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E9B841EE4878283ECDB554 /* InviteUsersScreen.swift */; };
|
||||
C4E0D03DF88242697545A9B7 /* UserIndicatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1275D9CE0FFBA6E8E85426 /* UserIndicatorController.swift */; };
|
||||
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; };
|
||||
|
@ -570,6 +576,7 @@
|
|||
E67418DACEDBC29E988E6ACD /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; };
|
||||
E75CE800B3E64D0F7F8E228D /* TemplateScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08E9043618AE5B0BF7B07E1 /* TemplateScreenViewModelTests.swift */; };
|
||||
E77469C5CD7F7F58C0AC9752 /* test_pdf.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3FFDA99C98BE05F43A92343B /* test_pdf.pdf */; };
|
||||
E78D429F18071545BF661A52 /* RoomDetailsEditScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */; };
|
||||
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; };
|
||||
E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.swift */; };
|
||||
E9560744F7B0292E20ECE5F2 /* RoomDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */; };
|
||||
|
@ -596,6 +603,7 @@
|
|||
F07D88421A9BC4D03D4A5055 /* VideoRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */; };
|
||||
F0F82C3C848C865C3098AA52 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; };
|
||||
F118DD449066E594F63C697D /* RoomMemberProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */; };
|
||||
F16109A6F6DF03DA26D59233 /* RoomDetailsEditScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */; };
|
||||
F18CA61A58C77C84F551B8E7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57916A1578D8043BB0795441 /* GeneratedMocks.swift */; };
|
||||
F253AAB4C8F06208173C9C4A /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||
F257F964493A9CD02A6F720C /* OnboardingPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF2717AB91060260E5F4781 /* OnboardingPageView.swift */; };
|
||||
|
@ -681,6 +689,7 @@
|
|||
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = "<group>"; };
|
||||
00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModelTests.swift; sourceTree = "<group>"; };
|
||||
00B62EE933FC3D5651AF4607 /* TimelineEventProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineEventProxy.swift; sourceTree = "<group>"; };
|
||||
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
01C4C7DB37597D7D8379511A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
024F7398C5FC12586FB10E9D /* EffectsScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsScene.swift; sourceTree = "<group>"; };
|
||||
0287793F11C480E242B03DF5 /* UserDiscoveryServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryServiceTest.swift; sourceTree = "<group>"; };
|
||||
|
@ -704,6 +713,7 @@
|
|||
095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementToggleStyle.swift; sourceTree = "<group>"; };
|
||||
099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiProviderTests.swift; sourceTree = "<group>"; };
|
||||
0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = "<group>"; };
|
||||
0C671107BDFC6CD1778C0B4C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -723,6 +733,7 @@
|
|||
111B698739E3410E2CDB7144 /* MXLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; };
|
||||
11F7F3CF7E70518BD7D25E04 /* EmojiMartEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartEmoji.swift; sourceTree = "<group>"; };
|
||||
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenUITests.swift; sourceTree = "<group>"; };
|
||||
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = "<group>"; };
|
||||
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
||||
|
@ -735,6 +746,7 @@
|
|||
1454CF3AABD242F55C8A2615 /* InviteUsersScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenModels.swift; sourceTree = "<group>"; };
|
||||
15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = "<group>"; };
|
||||
16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||
16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenModels.swift; sourceTree = "<group>"; };
|
||||
16DC8C5B2991724903F1FA6A /* AppIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = AppIcon.pdf; sourceTree = "<group>"; };
|
||||
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = "<group>"; };
|
||||
1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -754,6 +766,7 @@
|
|||
1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = "<group>"; };
|
||||
1DF2717AB91060260E5F4781 /* OnboardingPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageView.swift; sourceTree = "<group>"; };
|
||||
1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DTHTMLElement+AttributedStringBuilder.swift"; sourceTree = "<group>"; };
|
||||
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilder.swift; sourceTree = "<group>"; };
|
||||
1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||
|
@ -894,6 +907,7 @@
|
|||
6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridableAvatarImage.swift; sourceTree = "<group>"; };
|
||||
6493AC9979CEB1410302BFE3 /* RoomDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
|
@ -1165,6 +1179,7 @@
|
|||
D8E057FB1F07A5C201C89061 /* MockServerSelectionScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerSelectionScreenState.swift; sourceTree = "<group>"; };
|
||||
D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
DA2AEC1AB349A341FE13DEC1 /* StartChatScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenUITests.swift; sourceTree = "<group>"; };
|
||||
DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreen.swift; sourceTree = "<group>"; };
|
||||
DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedUserDefaultsKeys.swift; sourceTree = "<group>"; };
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = "<group>"; };
|
||||
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1183,6 +1198,7 @@
|
|||
E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
|
||||
E39CCFA7537FAD50386FDA00 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsClientProtocol.swift; sourceTree = "<group>"; };
|
||||
E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorModalView.swift; sourceTree = "<group>"; };
|
||||
E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||
|
@ -1610,6 +1626,7 @@
|
|||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */,
|
||||
C352359663A0E52BA20761EE /* LoadableImage.swift */,
|
||||
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */,
|
||||
648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */,
|
||||
C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */,
|
||||
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */,
|
||||
923485F85E1D765EF9D20E88 /* UserProfileCell.swift */,
|
||||
|
@ -2004,6 +2021,14 @@
|
|||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5F6CB68B44F6C587E463A934 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
605F8221E52991786397FCC9 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2176,6 +2201,7 @@
|
|||
00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */,
|
||||
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */,
|
||||
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
|
||||
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */,
|
||||
2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */,
|
||||
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
|
||||
69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */,
|
||||
|
@ -2491,6 +2517,7 @@
|
|||
39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */,
|
||||
0C88046D6A070D9827181C4D /* OnboardingUITests.swift */,
|
||||
4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */,
|
||||
122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */,
|
||||
3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */,
|
||||
0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */,
|
||||
C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */,
|
||||
|
@ -2940,6 +2967,7 @@
|
|||
3F38EAC92E2281990E65DAF2 /* OnboardingScreen */,
|
||||
A448A3A8F764174C60CD0CA1 /* Other */,
|
||||
5970F275D6014548DCED6106 /* ReportContentScreen */,
|
||||
E71742A824A7192C8D378875 /* RoomDetailsEditScreen */,
|
||||
E703BBD16266053B8A193C7B /* RoomDetailsScreen */,
|
||||
B86CF59E083C82C2A842E4AD /* RoomMemberDetailsScreen */,
|
||||
D4DB8163C10389C069458252 /* RoomMemberListScreen */,
|
||||
|
@ -2986,6 +3014,18 @@
|
|||
path = RoomDetailsScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E71742A824A7192C8D378875 /* RoomDetailsEditScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */,
|
||||
16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */,
|
||||
E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */,
|
||||
1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */,
|
||||
5F6CB68B44F6C587E463A934 /* View */,
|
||||
);
|
||||
path = RoomDetailsEditScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E74CD7681375AD2EAA34D66B /* Authentication */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3559,6 +3599,7 @@
|
|||
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
|
||||
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
|
||||
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
|
||||
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */,
|
||||
EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */,
|
||||
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
|
||||
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */,
|
||||
|
@ -3820,6 +3861,7 @@
|
|||
7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */,
|
||||
CE7148E80F09B7305E026AC6 /* OnboardingViewModel.swift in Sources */,
|
||||
992477AB8E3F3C36D627D32E /* OnboardingViewModelProtocol.swift in Sources */,
|
||||
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */,
|
||||
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
||||
7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */,
|
||||
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */,
|
||||
|
@ -3841,6 +3883,11 @@
|
|||
8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */,
|
||||
A494741843F087881299ACF0 /* RestorationToken.swift in Sources */,
|
||||
755EE5B0998C6A4D764D86E5 /* RoomAttachmentPicker.swift in Sources */,
|
||||
0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */,
|
||||
E78D429F18071545BF661A52 /* RoomDetailsEditScreenCoordinator.swift in Sources */,
|
||||
A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */,
|
||||
659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */,
|
||||
C49FCC766673006B6D299F1C /* RoomDetailsEditScreenViewModelProtocol.swift in Sources */,
|
||||
126EE01D8BEAEF26105D83C5 /* RoomDetailsScreen.swift in Sources */,
|
||||
FA5A7E32B1920FCB4EEDC1BA /* RoomDetailsScreenCoordinator.swift in Sources */,
|
||||
DB079D1929B5A5F52D207C83 /* RoomDetailsScreenModels.swift in Sources */,
|
||||
|
@ -4032,6 +4079,7 @@
|
|||
7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */,
|
||||
6B15FF984906AAFCF9DC4F58 /* OnboardingUITests.swift in Sources */,
|
||||
BA0D3DDCEDD97502DAC4B6E9 /* ReportContentScreenUITests.swift in Sources */,
|
||||
F16109A6F6DF03DA26D59233 /* RoomDetailsEditScreenUITests.swift in Sources */,
|
||||
829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */,
|
||||
A8771F5975A82759FA5138AE /* RoomMemberDetailsScreenUITests.swift in Sources */,
|
||||
44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */,
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
"common_file" = "File";
|
||||
"common_gif" = "GIF";
|
||||
"common_image" = "Image";
|
||||
"common_invite_unknown_profile" = "We can’t validate this user’s Matrix ID. The invite might not be received.";
|
||||
"common_leaving_room" = "Leaving room";
|
||||
"common_link_copied_to_clipboard" = "Link copied to clipboard";
|
||||
"common_loading" = "Loading…";
|
||||
|
@ -85,6 +86,7 @@
|
|||
"common_replying_to" = "Replying to %1$@";
|
||||
"common_report_a_bug" = "Report a bug";
|
||||
"common_report_submitted" = "Report submitted";
|
||||
"common_room_name" = "Room name";
|
||||
"common_search_for_someone" = "Search for someone";
|
||||
"common_search_results" = "Search results";
|
||||
"common_security" = "Security";
|
||||
|
@ -224,9 +226,12 @@
|
|||
"screen_room_attachment_source_camera_video" = "Record a video";
|
||||
"screen_room_attachment_source_files" = "Attachment";
|
||||
"screen_room_attachment_source_gallery" = "Photo & Video Library";
|
||||
"screen_room_details_add_topic_title" = "Add topic";
|
||||
"screen_room_details_already_a_member" = "Already a member";
|
||||
"screen_room_details_already_invited" = "Already invited";
|
||||
"screen_room_details_edition_error" = "An error occurred when updating the room details";
|
||||
"screen_room_details_edit_room_title" = "Edit Room";
|
||||
"screen_room_details_edition_error" = "We were unable to update all the information for this room.";
|
||||
"screen_room_details_edition_error_title" = "Unable to update room";
|
||||
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
|
||||
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
|
||||
"screen_room_details_share_room_title" = "Share room";
|
||||
|
@ -259,7 +264,6 @@
|
|||
"screen_signout_confirmation_dialog_title" = "Sign out";
|
||||
"screen_signout_in_progress_dialog_content" = "Signing out…";
|
||||
"screen_start_chat_error_starting_chat" = "An error occurred when trying to start a chat";
|
||||
"screen_start_chat_unknown_profile" = "We can’t validate this user’s Matrix ID. The invite might not be received.";
|
||||
"session_verification_banner_message" = "Looks like you’re using a new device. Verify it’s you to access your encrypted messages.";
|
||||
"session_verification_banner_title" = "Access your message history";
|
||||
"settings_rageshake" = "Rageshake";
|
||||
|
|
|
@ -29,6 +29,7 @@ final class AppSettings {
|
|||
case shouldCollapseRoomStateEvents
|
||||
case startChatFlowEnabled
|
||||
case startChatUserSuggestionsEnabled
|
||||
case editRoomDetailsFlowEnabled
|
||||
case invitesFlowEnabled
|
||||
case inviteMorePeopleFlowEnabled
|
||||
case readReceiptsEnabled
|
||||
|
@ -196,4 +197,9 @@ final class AppSettings {
|
|||
|
||||
@UserPreference(key: UserDefaultsKeys.readReceiptsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var readReceiptsEnabled
|
||||
|
||||
// MARK: Room details edit
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.editRoomDetailsFlowEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var editRoomDetailsFlowEnabled
|
||||
}
|
||||
|
|
|
@ -154,6 +154,8 @@ public enum L10n {
|
|||
public static var commonGif: String { return L10n.tr("Localizable", "common_gif") }
|
||||
/// Image
|
||||
public static var commonImage: String { return L10n.tr("Localizable", "common_image") }
|
||||
/// We can’t validate this user’s Matrix ID. The invite might not be received.
|
||||
public static var commonInviteUnknownProfile: String { return L10n.tr("Localizable", "common_invite_unknown_profile") }
|
||||
/// Leaving room
|
||||
public static var commonLeavingRoom: String { return L10n.tr("Localizable", "common_leaving_room") }
|
||||
/// Link copied to clipboard
|
||||
|
@ -192,6 +194,8 @@ public enum L10n {
|
|||
public static var commonReportABug: String { return L10n.tr("Localizable", "common_report_a_bug") }
|
||||
/// Report submitted
|
||||
public static var commonReportSubmitted: String { return L10n.tr("Localizable", "common_report_submitted") }
|
||||
/// Room name
|
||||
public static var commonRoomName: String { return L10n.tr("Localizable", "common_room_name") }
|
||||
/// Search for someone
|
||||
public static var commonSearchForSomeone: String { return L10n.tr("Localizable", "common_search_for_someone") }
|
||||
/// Search results
|
||||
|
@ -586,12 +590,18 @@ public enum L10n {
|
|||
public static var screenRoomAttachmentSourceFiles: String { return L10n.tr("Localizable", "screen_room_attachment_source_files") }
|
||||
/// Photo & Video Library
|
||||
public static var screenRoomAttachmentSourceGallery: String { return L10n.tr("Localizable", "screen_room_attachment_source_gallery") }
|
||||
/// Add topic
|
||||
public static var screenRoomDetailsAddTopicTitle: String { return L10n.tr("Localizable", "screen_room_details_add_topic_title") }
|
||||
/// Already a member
|
||||
public static var screenRoomDetailsAlreadyAMember: String { return L10n.tr("Localizable", "screen_room_details_already_a_member") }
|
||||
/// Already invited
|
||||
public static var screenRoomDetailsAlreadyInvited: String { return L10n.tr("Localizable", "screen_room_details_already_invited") }
|
||||
/// An error occurred when updating the room details
|
||||
/// Edit Room
|
||||
public static var screenRoomDetailsEditRoomTitle: String { return L10n.tr("Localizable", "screen_room_details_edit_room_title") }
|
||||
/// We were unable to update all the information for this room.
|
||||
public static var screenRoomDetailsEditionError: String { return L10n.tr("Localizable", "screen_room_details_edition_error") }
|
||||
/// Unable to update room
|
||||
public static var screenRoomDetailsEditionErrorTitle: String { return L10n.tr("Localizable", "screen_room_details_edition_error_title") }
|
||||
/// Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.
|
||||
public static var screenRoomDetailsEncryptionEnabledSubtitle: String { return L10n.tr("Localizable", "screen_room_details_encryption_enabled_subtitle") }
|
||||
/// Message encryption enabled
|
||||
|
@ -680,8 +690,6 @@ public enum L10n {
|
|||
public static var screenSignoutPreferenceItem: String { return L10n.tr("Localizable", "screen_signout_preference_item") }
|
||||
/// An error occurred when trying to start a chat
|
||||
public static var screenStartChatErrorStartingChat: String { return L10n.tr("Localizable", "screen_start_chat_error_starting_chat") }
|
||||
/// We can’t validate this user’s Matrix ID. The invite might not be received.
|
||||
public static var screenStartChatUnknownProfile: String { return L10n.tr("Localizable", "screen_start_chat_unknown_profile") }
|
||||
/// Looks like you’re using a new device. Verify it’s you to access your encrypted messages.
|
||||
public static var sessionVerificationBannerMessage: String { return L10n.tr("Localizable", "session_verification_banner_message") }
|
||||
/// Access your message history
|
||||
|
|
|
@ -371,6 +371,27 @@ class RoomMemberProxyMock: RoomMemberProxyProtocol {
|
|||
return unignoreUserReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - canSendStateEvent
|
||||
|
||||
var canSendStateEventTypeCallsCount = 0
|
||||
var canSendStateEventTypeCalled: Bool {
|
||||
return canSendStateEventTypeCallsCount > 0
|
||||
}
|
||||
var canSendStateEventTypeReceivedType: StateEventType?
|
||||
var canSendStateEventTypeReceivedInvocations: [StateEventType] = []
|
||||
var canSendStateEventTypeReturnValue: Bool!
|
||||
var canSendStateEventTypeClosure: ((StateEventType) -> Bool)?
|
||||
|
||||
func canSendStateEvent(type: StateEventType) -> Bool {
|
||||
canSendStateEventTypeCallsCount += 1
|
||||
canSendStateEventTypeReceivedType = type
|
||||
canSendStateEventTypeReceivedInvocations.append(type)
|
||||
if let canSendStateEventTypeClosure = canSendStateEventTypeClosure {
|
||||
return canSendStateEventTypeClosure(type)
|
||||
} else {
|
||||
return canSendStateEventTypeReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class RoomProxyMock: RoomProxyProtocol {
|
||||
var id: String {
|
||||
|
@ -419,11 +440,6 @@ class RoomProxyMock: RoomProxyProtocol {
|
|||
set(value) { underlyingMembersPublisher = value }
|
||||
}
|
||||
var underlyingMembersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never>!
|
||||
var updatesPublisher: AnyPublisher<TimelineDiff, Never> {
|
||||
get { return underlyingUpdatesPublisher }
|
||||
set(value) { underlyingUpdatesPublisher = value }
|
||||
}
|
||||
var underlyingUpdatesPublisher: AnyPublisher<TimelineDiff, Never>!
|
||||
var invitedMembersCount: UInt {
|
||||
get { return underlyingInvitedMembersCount }
|
||||
set(value) { underlyingInvitedMembersCount = value }
|
||||
|
@ -439,6 +455,11 @@ class RoomProxyMock: RoomProxyProtocol {
|
|||
set(value) { underlyingActiveMembersCount = value }
|
||||
}
|
||||
var underlyingActiveMembersCount: UInt!
|
||||
var updatesPublisher: AnyPublisher<TimelineDiff, Never> {
|
||||
get { return underlyingUpdatesPublisher }
|
||||
set(value) { underlyingUpdatesPublisher = value }
|
||||
}
|
||||
var underlyingUpdatesPublisher: AnyPublisher<TimelineDiff, Never>!
|
||||
|
||||
//MARK: - loadAvatarURLForUserId
|
||||
|
||||
|
@ -884,6 +905,86 @@ class RoomProxyMock: RoomProxyProtocol {
|
|||
return inviteUserIDReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - setName
|
||||
|
||||
var setNameCallsCount = 0
|
||||
var setNameCalled: Bool {
|
||||
return setNameCallsCount > 0
|
||||
}
|
||||
var setNameReceivedName: String?
|
||||
var setNameReceivedInvocations: [String?] = []
|
||||
var setNameReturnValue: Result<Void, RoomProxyError>!
|
||||
var setNameClosure: ((String?) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func setName(_ name: String?) async -> Result<Void, RoomProxyError> {
|
||||
setNameCallsCount += 1
|
||||
setNameReceivedName = name
|
||||
setNameReceivedInvocations.append(name)
|
||||
if let setNameClosure = setNameClosure {
|
||||
return await setNameClosure(name)
|
||||
} else {
|
||||
return setNameReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - setTopic
|
||||
|
||||
var setTopicCallsCount = 0
|
||||
var setTopicCalled: Bool {
|
||||
return setTopicCallsCount > 0
|
||||
}
|
||||
var setTopicReceivedTopic: String?
|
||||
var setTopicReceivedInvocations: [String] = []
|
||||
var setTopicReturnValue: Result<Void, RoomProxyError>!
|
||||
var setTopicClosure: ((String) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func setTopic(_ topic: String) async -> Result<Void, RoomProxyError> {
|
||||
setTopicCallsCount += 1
|
||||
setTopicReceivedTopic = topic
|
||||
setTopicReceivedInvocations.append(topic)
|
||||
if let setTopicClosure = setTopicClosure {
|
||||
return await setTopicClosure(topic)
|
||||
} else {
|
||||
return setTopicReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - removeAvatar
|
||||
|
||||
var removeAvatarCallsCount = 0
|
||||
var removeAvatarCalled: Bool {
|
||||
return removeAvatarCallsCount > 0
|
||||
}
|
||||
var removeAvatarReturnValue: Result<Void, RoomProxyError>!
|
||||
var removeAvatarClosure: (() async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func removeAvatar() async -> Result<Void, RoomProxyError> {
|
||||
removeAvatarCallsCount += 1
|
||||
if let removeAvatarClosure = removeAvatarClosure {
|
||||
return await removeAvatarClosure()
|
||||
} else {
|
||||
return removeAvatarReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - uploadAvatar
|
||||
|
||||
var uploadAvatarMediaCallsCount = 0
|
||||
var uploadAvatarMediaCalled: Bool {
|
||||
return uploadAvatarMediaCallsCount > 0
|
||||
}
|
||||
var uploadAvatarMediaReceivedMedia: MediaInfo?
|
||||
var uploadAvatarMediaReceivedInvocations: [MediaInfo] = []
|
||||
var uploadAvatarMediaReturnValue: Result<Void, RoomProxyError>!
|
||||
var uploadAvatarMediaClosure: ((MediaInfo) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func uploadAvatar(media: MediaInfo) async -> Result<Void, RoomProxyError> {
|
||||
uploadAvatarMediaCallsCount += 1
|
||||
uploadAvatarMediaReceivedMedia = media
|
||||
uploadAvatarMediaReceivedInvocations.append(media)
|
||||
if let uploadAvatarMediaClosure = uploadAvatarMediaClosure {
|
||||
return await uploadAvatarMediaClosure(media)
|
||||
} else {
|
||||
return uploadAvatarMediaReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
|
|
|
@ -23,11 +23,12 @@ struct RoomMemberProxyMockConfiguration {
|
|||
var avatarURL: URL?
|
||||
var membership: MembershipState
|
||||
var isNameAmbiguous = false
|
||||
var powerLevel: Int
|
||||
var normalizedPowerLevel: Int
|
||||
var powerLevel = 50
|
||||
var normalizedPowerLevel = 50
|
||||
var isAccountOwner = false
|
||||
var isIgnored = false
|
||||
var canInviteUsers = false
|
||||
var canSendStateEvent: (StateEventType) -> Bool = { _ in true }
|
||||
}
|
||||
|
||||
extension RoomMemberProxyMock {
|
||||
|
@ -43,6 +44,7 @@ extension RoomMemberProxyMock {
|
|||
isAccountOwner = configuration.isAccountOwner
|
||||
isIgnored = configuration.isIgnored
|
||||
canInviteUsers = configuration.canInviteUsers
|
||||
canSendStateEventTypeClosure = configuration.canSendStateEvent
|
||||
}
|
||||
|
||||
// Mocks
|
||||
|
@ -50,45 +52,35 @@ extension RoomMemberProxyMock {
|
|||
RoomMemberProxyMock(with: .init(userID: "@alice:matrix.org",
|
||||
displayName: "Alice",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockInvitedAlice: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@alice:matrix.org",
|
||||
displayName: "Alice",
|
||||
avatarURL: nil,
|
||||
membership: .invite,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .invite))
|
||||
}
|
||||
|
||||
static var mockBob: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@bob:matrix.org",
|
||||
displayName: "Bob",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockCharlie: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@charlie:matrix.org",
|
||||
displayName: "Charlie",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockDan: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@dan:matrix.org",
|
||||
displayName: "Dan",
|
||||
avatarURL: URL.picturesDirectory,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockMe: RoomMemberProxyMock {
|
||||
|
@ -96,8 +88,6 @@ extension RoomMemberProxyMock {
|
|||
displayName: "Me",
|
||||
avatarURL: URL.picturesDirectory,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50,
|
||||
isAccountOwner: true,
|
||||
canInviteUsers: true))
|
||||
}
|
||||
|
@ -107,8 +97,14 @@ extension RoomMemberProxyMock {
|
|||
displayName: "Ignored",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50,
|
||||
isIgnored: true))
|
||||
}
|
||||
|
||||
static func mockOwner(allowedStateEvents: [StateEventType]) -> RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@foo:some.org",
|
||||
displayName: "User owner",
|
||||
membership: .join,
|
||||
isAccountOwner: true,
|
||||
canSendStateEvent: { allowedStateEvents.contains($0) }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import Foundation
|
|||
|
||||
struct RoomProxyMockConfiguration {
|
||||
var id = UUID().uuidString
|
||||
let name: String? = nil
|
||||
var name: String?
|
||||
let displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
|
@ -75,5 +75,7 @@ extension RoomProxyMock {
|
|||
acceptInvitationClosure = { .success(()) }
|
||||
registerTimelineListenerIfNeededClosure = { .success([]) }
|
||||
underlyingUpdatesPublisher = Empty(completeImmediately: false).eraseToAnyPublisher()
|
||||
setNameClosure = { _ in .success(()) }
|
||||
setTopicClosure = { _ in .success(()) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ struct A11yIdentifiers {
|
|||
}
|
||||
|
||||
struct RoomDetailsScreen {
|
||||
let addTopic = "room_details-add_topic"
|
||||
let avatar = "room_details-avatar"
|
||||
let dmAvatar = "room_details-dm_avatar"
|
||||
let people = "room_details-people"
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct OverridableAvatarImage: View {
|
||||
private let overrideURL: URL?
|
||||
private let url: URL?
|
||||
private let name: String?
|
||||
private let contentID: String?
|
||||
private let avatarSize: AvatarSize
|
||||
private let imageProvider: ImageProviderProtocol?
|
||||
|
||||
@ScaledMetric private var frameSize: CGFloat
|
||||
|
||||
init(overrideURL: URL?, url: URL?, name: String?, contentID: String?, avatarSize: AvatarSize, imageProvider: ImageProviderProtocol?) {
|
||||
self.overrideURL = overrideURL
|
||||
self.url = url
|
||||
self.name = name
|
||||
self.contentID = contentID
|
||||
self.avatarSize = avatarSize
|
||||
self.imageProvider = imageProvider
|
||||
|
||||
_frameSize = ScaledMetric(wrappedValue: avatarSize.value)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let overrideURL {
|
||||
AsyncImage(url: overrideURL) { image in
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
.frame(width: frameSize, height: frameSize)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
LoadableAvatarImage(url: url,
|
||||
name: name,
|
||||
contentID: contentID,
|
||||
avatarSize: avatarSize,
|
||||
imageProvider: imageProvider)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ struct UserProfileCell: View {
|
|||
Text(Image(systemName: "exclamationmark.circle.fill"))
|
||||
.foregroundColor(.compound.textCriticalPrimary)
|
||||
|
||||
Text(L10n.screenStartChatUnknownProfile)
|
||||
Text(L10n.commonInviteUnknownProfile)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.font(.compound.bodyXS)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
class MockUserIndicatorController: UserIndicatorControllerProtocol {
|
||||
|
@ -23,5 +24,11 @@ class MockUserIndicatorController: UserIndicatorControllerProtocol {
|
|||
|
||||
func retractAllIndicators() { }
|
||||
|
||||
var alertInfo: AlertInfo<UUID>?
|
||||
var alertInfo: AlertInfo<UUID>? {
|
||||
didSet {
|
||||
alertInfoPublisher.send(alertInfo)
|
||||
}
|
||||
}
|
||||
|
||||
let alertInfoPublisher: PassthroughSubject<AlertInfo<UUID>?, Never> = .init()
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ struct DeveloperOptionsScreenViewStateBindings {
|
|||
var startChatUserSuggestionsEnabled: Bool
|
||||
var invitesFlowEnabled: Bool
|
||||
var inviteMorePeopleFlowEnabled: Bool
|
||||
var editRoomDetailsFlowEnabled: Bool
|
||||
}
|
||||
|
||||
enum DeveloperOptionsScreenViewAction {
|
||||
|
@ -38,5 +39,6 @@ enum DeveloperOptionsScreenViewAction {
|
|||
case changedStartChatUserSuggestionsEnabled
|
||||
case changedInvitesFlowEnabled
|
||||
case changedInviteMorePeopleFlowEnabled
|
||||
case changedEditRoomDetailsFlowEnabled
|
||||
case clearCache
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
|
|||
startChatFlowEnabled: appSettings.startChatFlowEnabled,
|
||||
startChatUserSuggestionsEnabled: appSettings.startChatUserSuggestionsEnabled,
|
||||
invitesFlowEnabled: appSettings.invitesFlowEnabled,
|
||||
inviteMorePeopleFlowEnabled: appSettings.inviteMorePeopleFlowEnabled)
|
||||
inviteMorePeopleFlowEnabled: appSettings.inviteMorePeopleFlowEnabled,
|
||||
editRoomDetailsFlowEnabled: appSettings.editRoomDetailsFlowEnabled)
|
||||
let state = DeveloperOptionsScreenViewState(bindings: bindings)
|
||||
|
||||
super.init(initialViewState: state)
|
||||
|
@ -51,6 +52,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
|
|||
appSettings.invitesFlowEnabled = state.bindings.invitesFlowEnabled
|
||||
case .changedInviteMorePeopleFlowEnabled:
|
||||
appSettings.inviteMorePeopleFlowEnabled = state.bindings.inviteMorePeopleFlowEnabled
|
||||
case .changedEditRoomDetailsFlowEnabled:
|
||||
appSettings.editRoomDetailsFlowEnabled = state.bindings.editRoomDetailsFlowEnabled
|
||||
case .clearCache:
|
||||
callback?(.clearCache)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,13 @@ struct DeveloperOptionsScreen: View {
|
|||
.onChange(of: context.inviteMorePeopleFlowEnabled) { _ in
|
||||
context.send(viewAction: .changedInviteMorePeopleFlowEnabled)
|
||||
}
|
||||
|
||||
Toggle(isOn: $context.editRoomDetailsFlowEnabled) {
|
||||
Text("Show edit room details flow")
|
||||
}
|
||||
.onChange(of: context.editRoomDetailsFlowEnabled) { _ in
|
||||
context.send(viewAction: .changedEditRoomDetailsFlowEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct RoomDetailsEditScreenCoordinatorParameters {
|
||||
let accountOwner: RoomMemberProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let navigationStackCoordinator: NavigationStackCoordinator
|
||||
let roomProxy: RoomProxyProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
enum RoomDetailsEditScreenCoordinatorAction {
|
||||
case dismiss
|
||||
}
|
||||
|
||||
final class RoomDetailsEditScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: RoomDetailsEditScreenCoordinatorParameters
|
||||
private var viewModel: RoomDetailsEditScreenViewModelProtocol
|
||||
private let actionsSubject: PassthroughSubject<RoomDetailsEditScreenCoordinatorAction, Never> = .init()
|
||||
private var cancellables: Set<AnyCancellable> = .init()
|
||||
|
||||
var actions: AnyPublisher<RoomDetailsEditScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: RoomDetailsEditScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = RoomDetailsEditScreenViewModel(accountOwner: parameters.accountOwner,
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
roomProxy: parameters.roomProxy,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actions
|
||||
.sink { [weak self] action in
|
||||
switch action {
|
||||
case .cancel, .saveFinished:
|
||||
self?.actionsSubject.send(.dismiss)
|
||||
case .displayCameraPicker:
|
||||
self?.displayMediaPickerWithSource(.camera)
|
||||
case .displayMediaPicker:
|
||||
self?.displayMediaPickerWithSource(.photoLibrary)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(RoomDetailsEditScreen(context: viewModel.context))
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func displayMediaPickerWithSource(_ source: MediaPickerScreenSource) {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
let userIndicatorController = UserIndicatorController(rootCoordinator: stackCoordinator)
|
||||
|
||||
let mediaPickerCoordinator = MediaPickerScreenCoordinator(userIndicatorController: userIndicatorController, source: source) { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .cancel:
|
||||
parameters.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .selectMediaAtURL(let url):
|
||||
parameters.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
viewModel.didSelectMediaUrl(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
stackCoordinator.setRootCoordinator(mediaPickerCoordinator)
|
||||
parameters.navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum RoomDetailsEditScreenViewModelAction {
|
||||
case cancel
|
||||
case saveFinished
|
||||
case displayCameraPicker
|
||||
case displayMediaPicker
|
||||
}
|
||||
|
||||
struct RoomDetailsEditScreenViewStateBindings {
|
||||
var name: String
|
||||
var topic: String
|
||||
var showMediaSheet = false
|
||||
}
|
||||
|
||||
struct RoomDetailsEditScreenViewState: BindableState {
|
||||
let roomID: String
|
||||
let initialAvatarURL: URL?
|
||||
let initialName: String
|
||||
let initialTopic: String
|
||||
let canEditAvatar: Bool
|
||||
let canEditName: Bool
|
||||
let canEditTopic: Bool
|
||||
var avatarURL: URL?
|
||||
var localMedia: MediaInfo?
|
||||
|
||||
var bindings: RoomDetailsEditScreenViewStateBindings
|
||||
|
||||
var nameDidChange: Bool {
|
||||
bindings.name != initialName
|
||||
}
|
||||
|
||||
var topicDidChange: Bool {
|
||||
bindings.topic != initialTopic
|
||||
}
|
||||
|
||||
var avatarDidChange: Bool {
|
||||
localMedia != nil || avatarURL != initialAvatarURL
|
||||
}
|
||||
|
||||
var canSave: Bool {
|
||||
!bindings.name.isEmpty && (avatarDidChange || nameDidChange || topicDidChange)
|
||||
}
|
||||
|
||||
var showDeleteImageAction: Bool {
|
||||
localMedia != nil || avatarURL != nil
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomDetailsEditScreenViewAction {
|
||||
case cancel
|
||||
case save
|
||||
case presentMediaSource
|
||||
case displayCameraPicker
|
||||
case displayMediaPicker
|
||||
case removeImage
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias RoomDetailsEditScreenViewModelType = StateStoreViewModel<RoomDetailsEditScreenViewState, RoomDetailsEditScreenViewAction>
|
||||
|
||||
class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDetailsEditScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<RoomDetailsEditScreenViewModelAction, Never> = .init()
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let mediaPreprocessor: MediaUploadingPreprocessor = .init()
|
||||
|
||||
var actions: AnyPublisher<RoomDetailsEditScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(accountOwner: RoomMemberProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
roomProxy: RoomProxyProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
let roomAvatar = roomProxy.avatarURL
|
||||
let roomName = roomProxy.name
|
||||
let roomTopic = roomProxy.topic
|
||||
|
||||
super.init(initialViewState: RoomDetailsEditScreenViewState(roomID: roomProxy.id,
|
||||
initialAvatarURL: roomAvatar,
|
||||
initialName: roomName ?? "",
|
||||
initialTopic: roomTopic ?? "",
|
||||
canEditAvatar: accountOwner.canSendStateEvent(type: .roomAvatar),
|
||||
canEditName: accountOwner.canSendStateEvent(type: .roomName),
|
||||
canEditTopic: accountOwner.canSendStateEvent(type: .roomTopic),
|
||||
avatarURL: roomAvatar,
|
||||
bindings: .init(name: roomName ?? "", topic: roomTopic ?? "")), imageProvider: mediaProvider)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: RoomDetailsEditScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
actionsSubject.send(.cancel)
|
||||
case .save:
|
||||
saveRoomDetails()
|
||||
case .presentMediaSource:
|
||||
state.bindings.showMediaSheet = true
|
||||
case .displayCameraPicker:
|
||||
actionsSubject.send(.displayCameraPicker)
|
||||
case .displayMediaPicker:
|
||||
actionsSubject.send(.displayMediaPicker)
|
||||
case .removeImage:
|
||||
if state.localMedia != nil {
|
||||
state.localMedia = nil
|
||||
} else {
|
||||
state.avatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func didSelectMediaUrl(url: URL) {
|
||||
Task {
|
||||
let userIndicatorID = UUID().uuidString
|
||||
defer {
|
||||
userIndicatorController.retractIndicatorWithId(userIndicatorID)
|
||||
}
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||
|
||||
let mediaResult = await mediaPreprocessor.processMedia(at: url)
|
||||
|
||||
switch mediaResult {
|
||||
case .success(.image):
|
||||
state.localMedia = try? mediaResult.get()
|
||||
case .failure, .success:
|
||||
userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError, message: L10n.errorUnknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func saveRoomDetails() {
|
||||
Task {
|
||||
let userIndicatorID = UUID().uuidString
|
||||
defer {
|
||||
userIndicatorController.retractIndicatorWithId(userIndicatorID)
|
||||
}
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.screenRoomDetailsUpdatingRoom, persistent: true))
|
||||
|
||||
do {
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
if state.avatarDidChange {
|
||||
group.addTask {
|
||||
if let localMedia = await self.state.localMedia {
|
||||
try await self.roomProxy.uploadAvatar(media: localMedia).get()
|
||||
} else if await self.state.avatarURL == nil {
|
||||
try await self.roomProxy.removeAvatar().get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.nameDidChange {
|
||||
group.addTask {
|
||||
try await self.roomProxy.setName(self.state.bindings.name).get()
|
||||
}
|
||||
}
|
||||
|
||||
if state.topicDidChange {
|
||||
group.addTask {
|
||||
try await self.roomProxy.setTopic(self.state.bindings.topic).get()
|
||||
}
|
||||
}
|
||||
|
||||
try await group.waitForAll()
|
||||
}
|
||||
|
||||
actionsSubject.send(.saveFinished)
|
||||
} catch {
|
||||
userIndicatorController.alertInfo = .init(id: .init(),
|
||||
title: L10n.screenRoomDetailsEditionErrorTitle,
|
||||
message: L10n.screenRoomDetailsEditionError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol RoomDetailsEditScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomDetailsEditScreenViewModelAction, Never> { get }
|
||||
var context: RoomDetailsEditScreenViewModelType.Context { get }
|
||||
|
||||
func didSelectMediaUrl(url: URL)
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RoomDetailsEditScreen: View {
|
||||
@ObservedObject var context: RoomDetailsEditScreenViewModel.Context
|
||||
@FocusState private var focus: Focus?
|
||||
|
||||
private enum Focus {
|
||||
case name
|
||||
case topic
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
mainContent
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.navigationTitle(L10n.screenRoomDetailsEditRoomTitle)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.actionCancel) {
|
||||
context.send(viewAction: .cancel)
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(L10n.actionSave) {
|
||||
context.send(viewAction: .save)
|
||||
focus = nil
|
||||
}
|
||||
.disabled(!context.viewState.canSave)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var mainContent: some View {
|
||||
Form {
|
||||
avatar
|
||||
nameSection
|
||||
topicSection
|
||||
}
|
||||
}
|
||||
|
||||
private var avatar: some View {
|
||||
Button {
|
||||
context.send(viewAction: .presentMediaSource)
|
||||
} label: {
|
||||
OverridableAvatarImage(overrideURL: context.viewState.localMedia?.thumbnailURL,
|
||||
url: context.viewState.avatarURL,
|
||||
name: context.viewState.initialName,
|
||||
contentID: context.viewState.roomID,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider)
|
||||
.overlay(alignment: .bottomTrailing) {
|
||||
if context.viewState.canEditAvatar {
|
||||
avatarOverlayIcon
|
||||
}
|
||||
}
|
||||
.confirmationDialog("", isPresented: $context.showMediaSheet) {
|
||||
mediaActionSheet
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(!context.viewState.canEditAvatar)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
|
||||
private var nameSection: some View {
|
||||
Section {
|
||||
let canEditName = context.viewState.canEditName
|
||||
|
||||
TextField(L10n.commonRoomName,
|
||||
text: $context.name,
|
||||
prompt: canEditName ? Text(L10n.screenCreateRoomRoomNamePlaceholder) : nil,
|
||||
axis: .horizontal)
|
||||
.focused($focus, equals: .name)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.disabled(!canEditName)
|
||||
.listRowBackground(canEditName ? nil : Color.clear)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
} header: {
|
||||
Text(L10n.commonRoomName)
|
||||
.formSectionHeader()
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var topicSection: some View {
|
||||
Section {
|
||||
let canEditTopic = context.viewState.canEditTopic
|
||||
|
||||
TextField(L10n.commonTopic,
|
||||
text: $context.topic,
|
||||
prompt: canEditTopic ? Text(L10n.screenCreateRoomTopicPlaceholder) : nil,
|
||||
axis: .vertical)
|
||||
.focused($focus, equals: .topic)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.disabled(!canEditTopic)
|
||||
.listRowBackground(canEditTopic ? nil : Color.clear)
|
||||
.lineLimit(3, reservesSpace: true)
|
||||
} header: {
|
||||
Text(L10n.commonTopic)
|
||||
.formSectionHeader()
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var avatarOverlayIcon: some View {
|
||||
Image(systemName: "camera")
|
||||
.padding(2)
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.white)
|
||||
.background {
|
||||
Circle()
|
||||
.foregroundColor(.black)
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var mediaActionSheet: some View {
|
||||
Button {
|
||||
context.send(viewAction: .displayCameraPicker)
|
||||
} label: {
|
||||
Text(L10n.actionTakePhoto)
|
||||
}
|
||||
Button {
|
||||
context.send(viewAction: .displayMediaPicker)
|
||||
} label: {
|
||||
Text(L10n.actionChoosePhoto)
|
||||
}
|
||||
if context.viewState.showDeleteImageAction {
|
||||
Button(role: .destructive) {
|
||||
context.send(viewAction: .removeImage)
|
||||
} label: {
|
||||
Text(L10n.actionRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct RoomDetailsEditScreen_Previews: PreviewProvider {
|
||||
static let viewModel = RoomDetailsEditScreenViewModel(accountOwner: RoomMemberProxyMock.mockAlice,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: RoomProxyMock(with: .init(name: "Room", displayName: "Room")),
|
||||
userIndicatorController: MockUserIndicatorController())
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
RoomDetailsEditScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,6 +62,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
|||
self.callback?(.cancel)
|
||||
case .leftRoom:
|
||||
self.callback?(.leftRoom)
|
||||
case .requestEditDetailsPresentation(let accountOwner):
|
||||
self.presentRoomDetailsEditScreen(accountOwner: accountOwner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +118,30 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
|||
parameters.navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
|
||||
}
|
||||
|
||||
private func presentRoomDetailsEditScreen(accountOwner: RoomMemberProxyProtocol) {
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let userIndicatorController = UserIndicatorController(rootCoordinator: navigationStackCoordinator)
|
||||
|
||||
let roomDetailsEditParameters = RoomDetailsEditScreenCoordinatorParameters(accountOwner: accountOwner,
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: parameters.roomProxy,
|
||||
userIndicatorController: userIndicatorController)
|
||||
let roomDetailsEditCoordinator = RoomDetailsEditScreenCoordinator(parameters: roomDetailsEditParameters)
|
||||
|
||||
roomDetailsEditCoordinator.actions.sink { [weak self] action in
|
||||
switch action {
|
||||
case .dismiss:
|
||||
self?.parameters.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(roomDetailsEditCoordinator)
|
||||
|
||||
parameters.navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
|
||||
}
|
||||
|
||||
private func toggleUser(_ user: UserProfile) {
|
||||
var selectedUsers = selectedUsers.value
|
||||
if let index = selectedUsers.firstIndex(where: { $0.userID == user.userID }) {
|
||||
|
|
|
@ -26,6 +26,7 @@ enum RoomDetailsScreenViewModelAction {
|
|||
case requestInvitePeoplePresentation([RoomMemberProxyProtocol])
|
||||
case leftRoom
|
||||
case cancel
|
||||
case requestEditDetailsPresentation(RoomMemberProxyProtocol)
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
@ -43,10 +44,21 @@ struct RoomDetailsScreenViewState: BindableState {
|
|||
var joinedMembersCount = 0
|
||||
var isProcessingIgnoreRequest = false
|
||||
var canInviteUsers = false
|
||||
var canEditRoomName = false
|
||||
var canEditRoomTopic = false
|
||||
var canEditRoomAvatar = false
|
||||
|
||||
var isLoadingMembers: Bool {
|
||||
members.isEmpty
|
||||
}
|
||||
|
||||
var canEdit: Bool {
|
||||
!isDirect && ServiceLocator.shared.settings.editRoomDetailsFlowEnabled && (canEditRoomName || canEditRoomTopic || canEditRoomAvatar)
|
||||
}
|
||||
|
||||
var hasTopicSection: Bool {
|
||||
topic != nil || (canEdit && canEditRoomTopic)
|
||||
}
|
||||
|
||||
var bindings: RoomDetailsScreenViewStateBindings
|
||||
|
||||
|
@ -130,6 +142,8 @@ enum RoomDetailsScreenViewAction {
|
|||
case processTapLeave
|
||||
case processTapIgnore
|
||||
case processTapUnignore
|
||||
case processTapEdit
|
||||
case processTapAddTopic
|
||||
case confirmLeave
|
||||
case ignoreConfirmed
|
||||
case unignoreConfirmed
|
||||
|
|
|
@ -23,6 +23,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||
private let roomProxy: RoomProxyProtocol
|
||||
private var members: [RoomMemberProxyProtocol] = []
|
||||
private var dmRecipient: RoomMemberProxyProtocol?
|
||||
private var accountOwner: RoomMemberProxyProtocol?
|
||||
|
||||
@CancellableTask
|
||||
private var buildMembersDetailsTask: Task<Void, Never>?
|
||||
|
@ -52,6 +53,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||
|
||||
// MARK: - Public
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
override func process(viewAction: RoomDetailsScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .processTapPeople:
|
||||
|
@ -71,6 +73,12 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||
state.bindings.ignoreUserRoomAlertItem = .init(action: .ignore)
|
||||
case .processTapUnignore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore)
|
||||
case .processTapEdit, .processTapAddTopic:
|
||||
guard let accountOwner else {
|
||||
MXLog.error("Missing account owner when presenting the room's edit details screen")
|
||||
return
|
||||
}
|
||||
callback?(.requestEditDetailsPresentation(accountOwner))
|
||||
case .ignoreConfirmed:
|
||||
Task { await ignore() }
|
||||
case .unignoreConfirmed:
|
||||
|
@ -105,13 +113,17 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||
self.state.joinedMembersCount = roomMembersDetails.joinedMembersCount
|
||||
self.state.dmRecipient = self.dmRecipient.map(RoomMemberDetails.init(withProxy:))
|
||||
self.state.canInviteUsers = roomMembersDetails.accountOwner?.canInviteUsers ?? false
|
||||
self.state.canEditRoomName = roomMembersDetails.accountOwner?.canSendStateEvent(type: .roomName) ?? false
|
||||
self.state.canEditRoomTopic = roomMembersDetails.accountOwner?.canSendStateEvent(type: .roomTopic) ?? false
|
||||
self.state.canEditRoomAvatar = roomMembersDetails.accountOwner?.canSendStateEvent(type: .roomAvatar) ?? false
|
||||
self.members = members
|
||||
self.accountOwner = roomMembersDetails.accountOwner
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.updatesPublisher
|
||||
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
|
||||
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.state.title = self.roomProxy.roomTitle
|
||||
|
|
|
@ -50,6 +50,15 @@ struct RoomDetailsScreen: View {
|
|||
.alert(item: $context.ignoreUserRoomAlertItem,
|
||||
actions: blockUserAlertActions,
|
||||
message: blockUserAlertMessage)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if context.viewState.canEdit {
|
||||
Button(L10n.actionEdit) {
|
||||
context.send(viewAction: .processTapEdit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
@ -98,11 +107,23 @@ struct RoomDetailsScreen: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var topicSection: some View {
|
||||
if let topic = context.viewState.topic {
|
||||
if context.viewState.hasTopicSection {
|
||||
Section {
|
||||
Text(topic)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.compound.bodySM)
|
||||
if let topic = context.viewState.topic, !topic.isEmpty {
|
||||
Text(topic)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.compound.bodySM)
|
||||
.lineLimit(3)
|
||||
} else {
|
||||
Button {
|
||||
context.send(viewAction: .processTapAddTopic)
|
||||
} label: {
|
||||
Text(L10n.screenRoomDetailsAddTopicTitle)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.compound.bodyLG)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.addTopic)
|
||||
}
|
||||
} header: {
|
||||
Text(L10n.commonTopic)
|
||||
.formSectionHeader()
|
||||
|
@ -142,7 +163,6 @@ struct RoomDetailsScreen: View {
|
|||
.listRowSeparatorTint(.element.quinaryContent)
|
||||
.buttonStyle(FormButtonStyle(accessory: context.viewState.isLoadingMembers ? nil : .navigationLink))
|
||||
.foregroundColor(.element.primaryContent)
|
||||
|
||||
.disabled(context.viewState.isLoadingMembers)
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,38 @@ enum MediaInfo {
|
|||
case video(videoURL: URL, thumbnailURL: URL, videoInfo: VideoInfo)
|
||||
case audio(audioURL: URL, audioInfo: AudioInfo)
|
||||
case file(fileURL: URL, fileInfo: FileInfo)
|
||||
|
||||
var mimeType: String? {
|
||||
switch self {
|
||||
case .image(_, _, let imageInfo):
|
||||
return imageInfo.mimetype
|
||||
case .video(_, _, let videoInfo):
|
||||
return videoInfo.mimetype
|
||||
case .audio(_, let audioInfo):
|
||||
return audioInfo.mimetype
|
||||
case .file(_, let fileInfo):
|
||||
return fileInfo.mimetype
|
||||
}
|
||||
}
|
||||
|
||||
var url: URL {
|
||||
switch self {
|
||||
case .image(let url, _, _),
|
||||
.video(let url, _, _),
|
||||
.audio(let url, _),
|
||||
.file(let url, _):
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
var thumbnailURL: URL? {
|
||||
switch self {
|
||||
case .image(_, let url, _), .video(_, let url, _):
|
||||
return url
|
||||
case .audio, .file:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ImageProcessingInfo {
|
||||
|
|
|
@ -70,6 +70,10 @@ final class RoomMemberProxy: RoomMemberProxyProtocol {
|
|||
var canInviteUsers: Bool {
|
||||
member.canInvite()
|
||||
}
|
||||
|
||||
func canSendStateEvent(type: StateEventType) -> Bool {
|
||||
member.canSendState(stateEvent: type)
|
||||
}
|
||||
|
||||
func ignoreUser() async -> Result<Void, RoomMemberProxyError> {
|
||||
sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true)
|
||||
|
|
|
@ -37,6 +37,7 @@ protocol RoomMemberProxyProtocol {
|
|||
|
||||
func ignoreUser() async -> Result<Void, RoomMemberProxyError>
|
||||
func unignoreUser() async -> Result<Void, RoomMemberProxyError>
|
||||
func canSendStateEvent(type: StateEventType) -> Bool
|
||||
}
|
||||
|
||||
extension RoomMemberProxyProtocol {
|
||||
|
|
|
@ -130,7 +130,19 @@ class RoomProxy: RoomProxyProtocol {
|
|||
// return trusted image for now, should be updated after verification status known
|
||||
return Asset.Images.encryptionTrusted.image
|
||||
}
|
||||
|
||||
var invitedMembersCount: UInt {
|
||||
UInt(room.invitedMembersCount())
|
||||
}
|
||||
|
||||
var joinedMembersCount: UInt {
|
||||
UInt(room.joinedMembersCount())
|
||||
}
|
||||
|
||||
var activeMembersCount: UInt {
|
||||
UInt(room.activeMembersCount())
|
||||
}
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError> {
|
||||
do {
|
||||
guard let urlString = try await Task.dispatch(on: lowPriorityDispatchQueue, {
|
||||
|
@ -486,18 +498,51 @@ class RoomProxy: RoomProxyProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
var invitedMembersCount: UInt {
|
||||
UInt(room.invitedMembersCount())
|
||||
func setName(_ name: String?) async -> Result<Void, RoomProxyError> {
|
||||
await Task.dispatch(on: .global()) {
|
||||
do {
|
||||
return try .success(self.room.setName(name: name))
|
||||
} catch {
|
||||
return .failure(.failedSettingRoomName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setTopic(_ topic: String) async -> Result<Void, RoomProxyError> {
|
||||
await Task.dispatch(on: .global()) {
|
||||
do {
|
||||
return try .success(self.room.setTopic(topic: topic))
|
||||
} catch {
|
||||
return .failure(.failedSettingRoomTopic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var joinedMembersCount: UInt {
|
||||
UInt(room.joinedMembersCount())
|
||||
func removeAvatar() async -> Result<Void, RoomProxyError> {
|
||||
await Task.dispatch(on: .global()) {
|
||||
do {
|
||||
return try .success(self.room.removeAvatar())
|
||||
} catch {
|
||||
return .failure(.failedRemovingAvatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var activeMembersCount: UInt {
|
||||
UInt(room.activeMembersCount())
|
||||
func uploadAvatar(media: MediaInfo) async -> Result<Void, RoomProxyError> {
|
||||
await Task.dispatch(on: .global()) {
|
||||
guard case let .image(imageURL, _, _) = media, let mimeType = media.mimeType else {
|
||||
return .failure(.failedUploadingAvatar)
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: imageURL)
|
||||
return try .success(self.room.uploadAvatar(mimeType: mimeType, data: [UInt8](data)))
|
||||
} catch {
|
||||
return .failure(.failedUploadingAvatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Force the timeline to load member details so it can populate sender profiles whenever we add a timeline listener
|
||||
|
|
|
@ -37,6 +37,10 @@ enum RoomProxyError: Error {
|
|||
case failedAcceptingInvite
|
||||
case failedRejectingInvite
|
||||
case failedInvitingUser
|
||||
case failedSettingRoomName
|
||||
case failedSettingRoomTopic
|
||||
case failedRemovingAvatar
|
||||
case failedUploadingAvatar
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -61,6 +65,12 @@ protocol RoomProxyProtocol {
|
|||
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> { get }
|
||||
|
||||
var invitedMembersCount: UInt { get }
|
||||
|
||||
var joinedMembersCount: UInt { get }
|
||||
|
||||
var activeMembersCount: UInt { get }
|
||||
|
||||
/// Publishes the room's updates.
|
||||
/// The publisher starts publishing after the first call to `registerTimelineListenerIfNeeded()`
|
||||
/// The thread on which this publisher sends the output isn't defined.
|
||||
|
@ -114,11 +124,13 @@ protocol RoomProxyProtocol {
|
|||
|
||||
func invite(userID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
var invitedMembersCount: UInt { get }
|
||||
func setName(_ name: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
var joinedMembersCount: UInt { get }
|
||||
func setTopic(_ topic: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
var activeMembersCount: UInt { get }
|
||||
func removeAvatar() async -> Result<Void, RoomProxyError>
|
||||
|
||||
func uploadAvatar(media: MediaInfo) async -> Result<Void, RoomProxyError>
|
||||
}
|
||||
|
||||
extension RoomProxyProtocol {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
|
@ -321,6 +322,24 @@ class MockScreen: Identifiable {
|
|||
userDiscoveryService: UserDiscoveryServiceMock()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomDetailsScreenWithEmptyTopic:
|
||||
ServiceLocator.shared.settings.editRoomDetailsFlowEnabled = true
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let members: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomTopic]), .mockBob, .mockCharlie]
|
||||
let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
topic: nil,
|
||||
avatarURL: URL.picturesDirectory,
|
||||
isDirect: false,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#mock:room.org",
|
||||
members: members))
|
||||
let coordinator = RoomDetailsScreenCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userDiscoveryService: UserDiscoveryServiceMock()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomDetailsScreenWithInvite:
|
||||
ServiceLocator.shared.settings.inviteMorePeopleFlowEnabled = true
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
@ -335,6 +354,21 @@ class MockScreen: Identifiable {
|
|||
userDiscoveryService: UserDiscoveryServiceMock()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomEditDetails, .roomEditDetailsReadOnly:
|
||||
let allowedStateEvents: [StateEventType] = id == .roomEditDetails ? [.roomAvatar, .roomName, .roomTopic] : []
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier",
|
||||
name: "Room",
|
||||
displayName: "Room",
|
||||
topic: "What a cool topic!",
|
||||
avatarURL: .picturesDirectory))
|
||||
let coordinator = RoomDetailsEditScreenCoordinator(parameters: .init(accountOwner: RoomMemberProxyMock.mockOwner(allowedStateEvents: allowedStateEvents),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: MockUserIndicatorController()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomMembersListScreen:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let members: [RoomMemberProxyMock] = [.mockAlice, .mockBob, .mockCharlie]
|
||||
|
|
|
@ -43,8 +43,11 @@ enum UITestsScreenIdentifier: String {
|
|||
case userSessionScreen
|
||||
case roomDetailsScreen
|
||||
case roomDetailsScreenWithRoomAvatar
|
||||
case roomDetailsScreenWithEmptyTopic
|
||||
case roomDetailsScreenWithInvite
|
||||
case roomDetailsScreenDmDetails
|
||||
case roomEditDetails
|
||||
case roomEditDetailsReadOnly
|
||||
case roomMembersListScreen
|
||||
case roomMembersListScreenPendingInvites
|
||||
case roomMemberDetailsAccountOwner
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import ElementX
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
class RoomDetailsEditScreenUITests: XCTestCase {
|
||||
func testEditableRoom() async throws {
|
||||
let app = Application.launch(.roomEditDetails)
|
||||
try await app.assertScreenshot(.roomEditDetails)
|
||||
}
|
||||
|
||||
func testReadOnlyRoom() async throws {
|
||||
let app = Application.launch(.roomEditDetailsReadOnly)
|
||||
try await app.assertScreenshot(.roomEditDetailsReadOnly)
|
||||
}
|
||||
}
|
|
@ -35,6 +35,13 @@ class RoomDetailsScreenUITests: XCTestCase {
|
|||
try await app.assertScreenshot(.roomDetailsScreenWithRoomAvatar)
|
||||
}
|
||||
|
||||
func testInitialStateComponentsWithEmptyTopic() async throws {
|
||||
let app = Application.launch(.roomDetailsScreenWithEmptyTopic)
|
||||
|
||||
XCTAssert(app.buttons[A11yIdentifiers.roomDetailsScreen.addTopic].waitForExistence(timeout: 1))
|
||||
try await app.assertScreenshot(.roomDetailsScreenWithEmptyTopic)
|
||||
}
|
||||
|
||||
func testInitialStateComponentsWithInvite() async throws {
|
||||
let app = Application.launch(.roomDetailsScreenWithInvite)
|
||||
|
||||
|
|
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEditDetails.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEditDetails.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEditDetails.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEditDetails.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEditDetails.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEditDetails.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomDetailsScreenWithEmptyTopic.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEditDetails.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEditDetails.png (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEditDetailsReadOnly.png (Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -65,9 +65,8 @@ class InviteUsersScreenViewModelTests: XCTestCase {
|
|||
_ = await viewModel.context.$viewState.values.first(where: { $0.membershipState.isEmpty == false })
|
||||
context.send(viewAction: .toggleUser(.mockAlice))
|
||||
|
||||
Task {
|
||||
await Task.yield()
|
||||
context.send(viewAction: .proceed)
|
||||
Task.detached(priority: .low) {
|
||||
await self.context.send(viewAction: .proceed)
|
||||
}
|
||||
|
||||
let action = await viewModel.actions.values.first()
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class RoomDetailsEditScreenViewModelTests: XCTestCase {
|
||||
var viewModel: RoomDetailsEditScreenViewModel!
|
||||
|
||||
var userIndicatorController: MockUserIndicatorController!
|
||||
|
||||
var context: RoomDetailsEditScreenViewModelType.Context {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
func testCannotSaveOnLanding() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
XCTAssertFalse(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testCanEdit() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
XCTAssertTrue(context.viewState.canEditAvatar)
|
||||
XCTAssertTrue(context.viewState.canEditName)
|
||||
XCTAssertTrue(context.viewState.canEditTopic)
|
||||
}
|
||||
|
||||
func testCannotEdit() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: []),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
XCTAssertFalse(context.viewState.canEditAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditName)
|
||||
XCTAssertFalse(context.viewState.canEditTopic)
|
||||
}
|
||||
|
||||
func testNameDidChange() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = "name"
|
||||
XCTAssertTrue(context.viewState.nameDidChange)
|
||||
XCTAssertTrue(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testTopicDidChange() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.topic = "topic"
|
||||
XCTAssertTrue(context.viewState.topicDidChange)
|
||||
XCTAssertTrue(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testAvatarDidChange() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room", avatarURL: .picturesDirectory))
|
||||
context.send(viewAction: .removeImage)
|
||||
XCTAssertTrue(context.viewState.avatarDidChange)
|
||||
XCTAssertTrue(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testEmptyNameCannotBeSaved() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = ""
|
||||
XCTAssertFalse(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testSaveShowsSheet() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = "name"
|
||||
XCTAssertFalse(context.showMediaSheet)
|
||||
context.send(viewAction: .presentMediaSource)
|
||||
XCTAssertTrue(context.showMediaSheet)
|
||||
}
|
||||
|
||||
func testSaveTriggersViewModelAction() async {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = "name"
|
||||
context.send(viewAction: .save)
|
||||
let action = await viewModel.actions.values.first()
|
||||
XCTAssertEqual(action, .saveFinished)
|
||||
}
|
||||
|
||||
func testErrorShownOnFailedFetchOfMedia() async throws {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
viewModel.didSelectMediaUrl(url: .picturesDirectory)
|
||||
|
||||
let alertInfo = await userIndicatorController.alertInfoPublisher
|
||||
.compactMap { $0 }
|
||||
.values
|
||||
.first()
|
||||
|
||||
XCTAssertNotNil(alertInfo)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupViewModel(accountOwner: RoomMemberProxyMock, roomProxyConfiguration: RoomProxyMockConfiguration) {
|
||||
userIndicatorController = MockUserIndicatorController()
|
||||
viewModel = .init(accountOwner: accountOwner,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: RoomProxyMock(with: roomProxyConfiguration),
|
||||
userIndicatorController: userIndicatorController)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ImageInfo {
|
||||
static let mock: ImageInfo = .init(height: nil,
|
||||
width: nil,
|
||||
mimetype: nil,
|
||||
size: nil,
|
||||
thumbnailInfo: nil,
|
||||
thumbnailSource: nil,
|
||||
blurhash: nil)
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
@ -27,6 +28,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||
override func setUp() {
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test"))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
AppSettings.reset()
|
||||
ServiceLocator.shared.settings.editRoomDetailsFlowEnabled = true
|
||||
}
|
||||
|
||||
func testLeaveRoomTappedWhenPublic() async {
|
||||
|
@ -219,4 +223,66 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||
await context.nextViewState()
|
||||
XCTAssertEqual(roomProxyMock.registerTimelineListenerIfNeededCallsCount, 1)
|
||||
}
|
||||
|
||||
func testCanEditAvatar() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertTrue(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
XCTAssertFalse(context.viewState.canEditRoomTopic)
|
||||
XCTAssertTrue(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCanEditName() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomName]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertTrue(context.viewState.canEditRoomName)
|
||||
XCTAssertFalse(context.viewState.canEditRoomTopic)
|
||||
XCTAssertTrue(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCanEditTopic() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomTopic]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
XCTAssertTrue(context.viewState.canEditRoomTopic)
|
||||
XCTAssertTrue(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCannotEditRoom() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: []), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
XCTAssertFalse(context.viewState.canEditRoomTopic)
|
||||
XCTAssertFalse(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCannotEditDirectRoom() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEdit)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue