Polls history (#2244)
This commit is contained in:
@@ -92,6 +92,7 @@
|
||||
1471A080552631358D152C18 /* AudioPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDB3E65A79779EDA5D33D8A /* AudioPlayerState.swift */; };
|
||||
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; };
|
||||
14E99D27628B1A6F0CB46FEA /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */; };
|
||||
151D2477F75782C8702F2873 /* PollInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */; };
|
||||
152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */; };
|
||||
153E22E8227F46545E5D681C /* PollRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C936FDD017808FE416742D64 /* PollRoomTimelineView.swift */; };
|
||||
155063E980E763D4910EA3CF /* Analytics+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */; };
|
||||
@@ -127,6 +128,7 @@
|
||||
1D623953F970D11F6F38499C /* AppLockService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851B95BB98649B8E773D6790 /* AppLockService.swift */; };
|
||||
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; };
|
||||
1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; };
|
||||
1EC6D1B58B24369734CD62BA /* PollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F41A4B5C4F457AF710666 /* PollView.swift */; };
|
||||
1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */; };
|
||||
1F3232BD368DF430AB433907 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; };
|
||||
1FE593ECEC40A43789105D80 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */; };
|
||||
@@ -315,6 +317,7 @@
|
||||
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; };
|
||||
5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */; };
|
||||
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; };
|
||||
51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; };
|
||||
51C240F4660F7269203A9B3A /* MigrationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */; };
|
||||
520EEDAFBC778AB0B41F2F53 /* ClientMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE6170EFE6A161B0A68AB61 /* ClientMock.swift */; };
|
||||
523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */; };
|
||||
@@ -410,7 +413,6 @@
|
||||
6AECC84BE14A13440120FED8 /* NSESettingsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */; };
|
||||
6B05AA5D9BBCD6D8D63B80EB /* TimelineItemAccessibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C6F3DAD167F972702C8893 /* TimelineItemAccessibilityModifier.swift */; };
|
||||
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; };
|
||||
6B4BF4A6450F55939B49FAEF /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67779D9A1B797285A09B7720 /* PollOptionView.swift */; };
|
||||
6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC23C63849452BC86EA2852 /* ButtonStyle.swift */; };
|
||||
6BB6944443C421C722ED1E7D /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */; };
|
||||
6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; };
|
||||
@@ -464,10 +466,13 @@
|
||||
784592335560C2E91D32D177 /* DeveloperOptionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */; };
|
||||
78A3392047E9D1C6FEA659B6 /* InvitesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33649299575BADC34924ABC6 /* InvitesScreenCoordinator.swift */; };
|
||||
795A854F63301DC6B46217B9 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; };
|
||||
79741C1953269FF1A211D246 /* RoomPollsHistoryScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */; };
|
||||
7A02EB29F3B993AB20E0A198 /* RoomPollsHistoryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */; };
|
||||
7A0A0929556792FB19B812C5 /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */; };
|
||||
7A642EE5F1ADC5D520F21924 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; };
|
||||
7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; };
|
||||
7AEC56ADEFC5A7198A17412F /* InviteUsersScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB35E2DB4EFE8E6F3959629 /* InviteUsersScreenUITests.swift */; };
|
||||
7B1605C6FFD4D195F264A684 /* RoomPollsHistoryScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */; };
|
||||
7B5DAB915357BE596529BF25 /* MapTilerStaticMapProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20872C3887F835958CE2F1D0 /* MapTilerStaticMapProtocol.swift */; };
|
||||
7BB31E67648CF32D2AB5E502 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */; };
|
||||
7BF368A78E6D9AFD222F25AF /* SecureBackupScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE094FCB6387D268C436161 /* SecureBackupScreenViewModel.swift */; };
|
||||
@@ -497,6 +502,7 @@
|
||||
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; };
|
||||
829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */; };
|
||||
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9227F7495DA43324050A863 /* TextBasedRoomTimelineItem.swift */; };
|
||||
835B7AD20407F766C747BEC5 /* RoomPollsHistoryScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D147EB979902DBBE452EADC /* RoomPollsHistoryScreenUITests.swift */; };
|
||||
83A4DAB181C56987C3E804FF /* MapTilerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */; };
|
||||
84226AD2E1F1FBC965F3B09E /* UnitTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E19C4645D3F5F9FB02355 /* UnitTestsAppCoordinator.swift */; };
|
||||
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */; };
|
||||
@@ -554,6 +560,7 @@
|
||||
9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */; };
|
||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||
92133B170A1F917685E9FF78 /* OnboardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D168471461717AF5689F64B /* OnboardingScreenUITests.swift */; };
|
||||
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
||||
@@ -884,6 +891,7 @@
|
||||
E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B410B32B72C90BF94E481F33 /* AppLockSetupPINScreenModels.swift */; };
|
||||
E4BAEED438A843D7B01D8069 /* CompletionSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F421E51DF00377DE1A01354 /* CompletionSuggestionView.swift */; };
|
||||
E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */; };
|
||||
E58F1F3276E98A93F7D39219 /* RoomPollsHistoryScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */; };
|
||||
E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; };
|
||||
E62EC30B39354A391E32A126 /* AudioRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */; };
|
||||
E67418DACEDBC29E988E6ACD /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; };
|
||||
@@ -933,6 +941,7 @@
|
||||
F07D88421A9BC4D03D4A5055 /* VideoRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */; };
|
||||
F0A26CD502C3A5868353B0FA /* ServerConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */; };
|
||||
F0F82C3C848C865C3098AA52 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; };
|
||||
F103924DED414ADFE398CE99 /* RoomPollsHistoryScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */; };
|
||||
F118DD449066E594F63C697D /* RoomMemberProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */; };
|
||||
F12F6BED7B6D7EE4BEE55039 /* PlainMentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE78FA0011E07920AE83135 /* PlainMentionBuilder.swift */; };
|
||||
F16109A6F6DF03DA26D59233 /* RoomDetailsEditScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */; };
|
||||
@@ -948,6 +957,7 @@
|
||||
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; };
|
||||
F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */; };
|
||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
||||
F50A6FCE26714E27FE5495DD /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */; };
|
||||
F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; };
|
||||
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; };
|
||||
F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; };
|
||||
@@ -1083,6 +1093,7 @@
|
||||
0C62E07C1164F5120727A2A8 /* AppLockSetupBiometricsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; };
|
||||
0D147EB979902DBBE452EADC /* RoomPollsHistoryScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenUITests.swift; sourceTree = "<group>"; };
|
||||
0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||
0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentionalMentions.swift; sourceTree = "<group>"; };
|
||||
@@ -1180,6 +1191,7 @@
|
||||
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyMock.swift; sourceTree = "<group>"; };
|
||||
24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandlerProtocol.swift; sourceTree = "<group>"; };
|
||||
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = "<group>"; };
|
||||
25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = "<group>"; };
|
||||
@@ -1216,6 +1228,7 @@
|
||||
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriver.swift; sourceTree = "<group>"; };
|
||||
30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = "<group>"; };
|
||||
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = "<group>"; };
|
||||
317F41A4B5C4F457AF710666 /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
||||
31A6314FDC51DA25712D9A81 /* PillContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContextTests.swift; sourceTree = "<group>"; };
|
||||
31B35311C7FED04B0E1B80C2 /* RoomMemberDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetails.swift; sourceTree = "<group>"; };
|
||||
31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
@@ -1280,6 +1293,7 @@
|
||||
422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; };
|
||||
42ADEA322D2089391E049535 /* InvitesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreen.swift; sourceTree = "<group>"; };
|
||||
42C64A14EE89928207E3B42B /* AnalyticsSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenModels.swift; sourceTree = "<group>"; };
|
||||
42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreen.swift; sourceTree = "<group>"; };
|
||||
42EEA67A6796BDC2761619C5 /* PaginationIndicatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
43A84EE187D0C772E18A4E39 /* VoiceMessageCacheProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageCacheProtocol.swift; sourceTree = "<group>"; };
|
||||
4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareToMapsAppActivity.swift; sourceTree = "<group>"; };
|
||||
@@ -1330,6 +1344,7 @@
|
||||
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; };
|
||||
50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
|
||||
514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkCoordinator.swift; sourceTree = "<group>"; };
|
||||
51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = "<group>"; };
|
||||
@@ -1405,7 +1420,6 @@
|
||||
66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||
669F35C505ACE1110589F875 /* MediaUploadingPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessor.swift; sourceTree = "<group>"; };
|
||||
66F2402D738694F98729A441 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = "<group>"; };
|
||||
67779D9A1B797285A09B7720 /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
|
||||
68010886142843705E342645 /* ProgressMaskModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressMaskModifier.swift; sourceTree = "<group>"; };
|
||||
6861FE915C7B5466E6962BBA /* StartChatScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreen.swift; sourceTree = "<group>"; };
|
||||
693E16574C6F7F9FA1015A8C /* Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = "<group>"; };
|
||||
@@ -1429,6 +1443,7 @@
|
||||
6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
6E5725BC6C63604CB769145B /* LegalInformationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
6E5E9C044BEB7C70B1378E91 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = "<group>"; };
|
||||
6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenModels.swift; sourceTree = "<group>"; };
|
||||
6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = "<group>"; };
|
||||
6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = "<group>"; };
|
||||
6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1590,6 +1605,7 @@
|
||||
A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerProtocol.swift; sourceTree = "<group>"; };
|
||||
A0A01AECFF54281CF35909A6 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = "<group>"; };
|
||||
A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenu.swift; sourceTree = "<group>"; };
|
||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1657,6 +1673,7 @@
|
||||
B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = "<group>"; };
|
||||
B3A1398EFF65090FDA1CB639 /* ProcessInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessInfo.swift; sourceTree = "<group>"; };
|
||||
B4005D82E9D27BAF006A8FE1 /* AppLockScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
B410B32B72C90BF94E481F33 /* AppLockSetupPINScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenModels.swift; sourceTree = "<group>"; };
|
||||
B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
B48B7AD4908C5C374517B892 /* MapAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MapAssets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -1778,6 +1795,7 @@
|
||||
D0C2D52E36AD614B3C003EF6 /* RoomTimelineItemViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemViewState.swift; sourceTree = "<group>"; };
|
||||
D121B4FCFC38DBCC17BCC6D6 /* ComposerToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbar.swift; sourceTree = "<group>"; };
|
||||
D1BC84BA0AF11C2128D58ABD /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
|
||||
D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
D263254AFE5B7993FFBBF324 /* NSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NSE.entitlements; sourceTree = "<group>"; };
|
||||
D26813CCE39221FE30BF22CD /* PlatformViewVersionPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewVersionPredicate.swift; sourceTree = "<group>"; };
|
||||
D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -1817,6 +1835,7 @@
|
||||
DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreen.swift; sourceTree = "<group>"; };
|
||||
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
|
||||
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandler.swift; sourceTree = "<group>"; };
|
||||
DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = "<group>"; };
|
||||
DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceTests.swift; sourceTree = "<group>"; };
|
||||
DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = "<group>"; };
|
||||
@@ -1885,6 +1904,7 @@
|
||||
F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStyle.swift; sourceTree = "<group>"; };
|
||||
F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
|
||||
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
|
||||
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
@@ -2115,6 +2135,7 @@
|
||||
6709362D60732DED2069AE0F /* MediaPlayer */,
|
||||
6DE13A7AE6587B079F4049D7 /* Notification */,
|
||||
114DC16B28140F885FD833E2 /* NotificationSettings */,
|
||||
599DFFE0805B08454E40D64A /* Polls */,
|
||||
40E6246F03D1FE377BC5D963 /* Room */,
|
||||
07900E9BFFD109F91B35B4C5 /* RoomMember */,
|
||||
BDCEF7C3BF6D09F5611CFC8B /* SecureBackup */,
|
||||
@@ -2762,6 +2783,15 @@
|
||||
path = AppLockSetupBiometricsScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
45778D52AECD4EB99A289214 /* Polls */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */,
|
||||
317F41A4B5C4F457AF710666 /* PollView.swift */,
|
||||
);
|
||||
path = Polls;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4775A7D6FBB210BF21318AD9 /* UserDetailsEditScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2974,6 +3004,15 @@
|
||||
path = ReportContentScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
599DFFE0805B08454E40D64A /* Polls */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */,
|
||||
259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */,
|
||||
);
|
||||
path = Polls;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5B2C520AB9863B8CBC8EB3CA /* SoftLogoutScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3244,6 +3283,7 @@
|
||||
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
|
||||
69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */,
|
||||
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */,
|
||||
B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */,
|
||||
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
|
||||
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */,
|
||||
2E88534A39781D76487D59DF /* SecureBackupKeyBackupScreenViewModelTests.swift */,
|
||||
@@ -3387,6 +3427,7 @@
|
||||
A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */,
|
||||
0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */,
|
||||
874A1842477895F199567BD7 /* TimelineView.swift */,
|
||||
45778D52AECD4EB99A289214 /* Polls */,
|
||||
4820FFB9F4FDDFD95763D498 /* ReadReceipts */,
|
||||
1D8572B713A11CFDBF009B2F /* Replies */,
|
||||
A312471EA62EFB0FD94E60DC /* Style */,
|
||||
@@ -3713,6 +3754,7 @@
|
||||
0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */,
|
||||
C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */,
|
||||
66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */,
|
||||
0D147EB979902DBBE452EADC /* RoomPollsHistoryScreenUITests.swift */,
|
||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */,
|
||||
58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */,
|
||||
1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */,
|
||||
@@ -4089,7 +4131,6 @@
|
||||
772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */,
|
||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */,
|
||||
42EEA67A6796BDC2761619C5 /* PaginationIndicatorRoomTimelineView.swift */,
|
||||
67779D9A1B797285A09B7720 /* PollOptionView.swift */,
|
||||
C936FDD017808FE416742D64 /* PollRoomTimelineView.swift */,
|
||||
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */,
|
||||
C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */,
|
||||
@@ -4306,6 +4347,14 @@
|
||||
path = Layout;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D4B487C81A239A9C71807601 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D4DB8163C10389C069458252 /* RoomMemberListScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -4326,6 +4375,18 @@
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D57B3BC211BB74420C9138D7 /* RoomPollsHistoryScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */,
|
||||
6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */,
|
||||
F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */,
|
||||
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */,
|
||||
D4B487C81A239A9C71807601 /* View */,
|
||||
);
|
||||
path = RoomPollsHistoryScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D977D4E565C06D3F41C8F8FC /* Virtual */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -4425,6 +4486,7 @@
|
||||
B86CF59E083C82C2A842E4AD /* RoomMemberDetailsScreen */,
|
||||
D4DB8163C10389C069458252 /* RoomMemberListScreen */,
|
||||
0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */,
|
||||
D57B3BC211BB74420C9138D7 /* RoomPollsHistoryScreen */,
|
||||
679E9837ECA8D6776079D16E /* RoomScreen */,
|
||||
2565414373E6F68005966B8E /* SecureBackup */,
|
||||
3153FCA3F4B0E88B16D99D12 /* SessionVerificationScreen */,
|
||||
@@ -5189,6 +5251,7 @@
|
||||
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
|
||||
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */,
|
||||
E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */,
|
||||
7B1605C6FFD4D195F264A684 /* RoomPollsHistoryScreenViewModelTests.swift in Sources */,
|
||||
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
|
||||
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */,
|
||||
7691233E3572A9173FD96CB3 /* SecureBackupKeyBackupScreenViewModelTests.swift in Sources */,
|
||||
@@ -5602,10 +5665,13 @@
|
||||
70B83D44043293B4B77440B9 /* PollFormScreenModels.swift in Sources */,
|
||||
F9EA79092C18A8CFE4922DD2 /* PollFormScreenViewModel.swift in Sources */,
|
||||
260FFC1475EE94F641C3F3F9 /* PollFormScreenViewModelProtocol.swift in Sources */,
|
||||
151D2477F75782C8702F2873 /* PollInteractionHandler.swift in Sources */,
|
||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */,
|
||||
16CBD087038DE3815CDA512C /* PollMock.swift in Sources */,
|
||||
6B4BF4A6450F55939B49FAEF /* PollOptionView.swift in Sources */,
|
||||
F50A6FCE26714E27FE5495DD /* PollOptionView.swift in Sources */,
|
||||
864C0D3A4077BF433DBC691F /* PollRoomTimelineItem.swift in Sources */,
|
||||
153E22E8227F46545E5D681C /* PollRoomTimelineView.swift in Sources */,
|
||||
1EC6D1B58B24369734CD62BA /* PollView.swift in Sources */,
|
||||
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
|
||||
FD4DEC88210F35C35B2FB386 /* ProcessInfo.swift in Sources */,
|
||||
69DE29C3E3180BB17D840690 /* ProgressCursorModifier.swift in Sources */,
|
||||
@@ -5668,6 +5734,11 @@
|
||||
E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */,
|
||||
BA4C9049BC96DED3A2F3B82E /* RoomNotificationSettingsScreenViewModelProtocol.swift in Sources */,
|
||||
491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */,
|
||||
7A02EB29F3B993AB20E0A198 /* RoomPollsHistoryScreen.swift in Sources */,
|
||||
E58F1F3276E98A93F7D39219 /* RoomPollsHistoryScreenCoordinator.swift in Sources */,
|
||||
51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */,
|
||||
79741C1953269FF1A211D246 /* RoomPollsHistoryScreenViewModel.swift in Sources */,
|
||||
F103924DED414ADFE398CE99 /* RoomPollsHistoryScreenViewModelProtocol.swift in Sources */,
|
||||
4FC1EFE4968A259CBBACFAFB /* RoomProxy.swift in Sources */,
|
||||
BD203FC6A7AE7637EA003643 /* RoomProxyMock.swift in Sources */,
|
||||
FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */,
|
||||
@@ -5937,6 +6008,7 @@
|
||||
A8771F5975A82759FA5138AE /* RoomMemberDetailsScreenUITests.swift in Sources */,
|
||||
44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */,
|
||||
06AA515C7053FD7E17A5CF81 /* RoomNotificationSettingsScreenUITests.swift in Sources */,
|
||||
835B7AD20407F766C747BEC5 /* RoomPollsHistoryScreenUITests.swift in Sources */,
|
||||
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */,
|
||||
A743841F91B62B0E56217B04 /* SecureBackupKeyBackupScreenUITests.swift in Sources */,
|
||||
F421FD5979EF53C8204BDC77 /* SecureBackupLogoutConfirmationScreenUITests.swift in Sources */,
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
"action_try_again" = "Try again";
|
||||
"action_view_source" = "View source";
|
||||
"action_yes" = "Yes";
|
||||
"action.load_more" = "Load more";
|
||||
"common_about" = "About";
|
||||
"common_acceptable_use_policy" = "Acceptable use policy";
|
||||
"common_advanced_settings" = "Advanced settings";
|
||||
@@ -444,6 +445,11 @@
|
||||
"screen_onboarding_welcome_message" = "Welcome to the fastest Element ever. Supercharged for speed and simplicity.";
|
||||
"screen_onboarding_welcome_subtitle" = "Welcome to %1$@. Supercharged, for speed and simplicity.";
|
||||
"screen_onboarding_welcome_title" = "Be in your element";
|
||||
"screen_polls_history_empty_ongoing" = "Can't find any ongoing polls.";
|
||||
"screen_polls_history_empty_past" = "Can't find any past polls.";
|
||||
"screen_polls_history_filter_ongoing" = "Ongoing";
|
||||
"screen_polls_history_filter_past" = "Past";
|
||||
"screen_polls_history_title" = "Polls";
|
||||
"screen_recovery_key_change_description" = "Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.";
|
||||
"screen_recovery_key_change_generate_key" = "Generate a new recovery key";
|
||||
"screen_recovery_key_change_generate_key_description" = "Make sure you can store your recovery key somewhere safe";
|
||||
|
||||
@@ -264,7 +264,7 @@ final class AppSettings {
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.chatBackupEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var chatBackupEnabled
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - Shared
|
||||
|
||||
@@ -206,12 +206,22 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
return .mapNavigator(roomID: roomID)
|
||||
case (.mapNavigator(let roomID), .dismissMapNavigator):
|
||||
return .room(roomID: roomID)
|
||||
|
||||
|
||||
case (.room(let roomID), .presentPollForm):
|
||||
return .pollForm(roomID: roomID)
|
||||
case (.pollForm(let roomID), .dismissPollForm):
|
||||
return .room(roomID: roomID)
|
||||
|
||||
|
||||
case (.roomDetails(let roomID, _), .presentPollsHistory):
|
||||
return .pollsHistory(roomID: roomID)
|
||||
case (.pollsHistory(let roomID), .dismissPollsHistory):
|
||||
return .roomDetails(roomID: roomID, isRoot: false)
|
||||
|
||||
case (.pollsHistory(let roomID), .presentPollForm):
|
||||
return .pollsHistoryForm(roomID: roomID)
|
||||
case (.pollsHistoryForm(let roomID), .dismissPollForm):
|
||||
return .pollsHistory(roomID: roomID)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -320,6 +330,16 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.pollForm, .dismissPollForm, .room):
|
||||
break
|
||||
|
||||
case (.roomDetails(let roomID, _), .presentPollsHistory, .pollsHistory):
|
||||
presentPollsHistory(roomID: roomID)
|
||||
case (.pollsHistory, .dismissPollsHistory, .roomDetails):
|
||||
break
|
||||
|
||||
case (.pollsHistory, .presentPollForm(let mode), .pollsHistoryForm):
|
||||
presentPollForm(mode: mode)
|
||||
case (.pollsHistoryForm, .dismissPollForm, .pollsHistory):
|
||||
break
|
||||
|
||||
default:
|
||||
fatalError("Unknown transition: \(context)")
|
||||
}
|
||||
@@ -516,6 +536,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.presentNotificationSettingsScreen)
|
||||
case .presentInviteUsersScreen:
|
||||
stateMachine.tryEvent(.presentInviteUsersScreen)
|
||||
case .presentPollsHistory:
|
||||
stateMachine.tryEvent(.presentPollsHistory)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -845,6 +867,58 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentPollsHistory(roomID: String) {
|
||||
Task {
|
||||
await asyncPresentRoomPollsHistory(roomID: roomID)
|
||||
}
|
||||
}
|
||||
|
||||
private func asyncPresentRoomPollsHistory(roomID: String) async {
|
||||
let roomProxy: RoomProxyProtocol
|
||||
|
||||
guard let proxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
MXLog.error("Invalid room identifier: \(roomID)")
|
||||
stateMachine.tryEvent(.dismissPollsHistory)
|
||||
return
|
||||
}
|
||||
|
||||
roomProxy = proxy
|
||||
|
||||
await roomProxy.subscribeForUpdates()
|
||||
|
||||
let userID = userSession.clientProxy.userID
|
||||
|
||||
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL,
|
||||
mentionBuilder: MentionBuilder()),
|
||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID),
|
||||
appSettings: appSettings)
|
||||
|
||||
let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy,
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
secureBackupController: userSession.clientProxy.secureBackupController)
|
||||
|
||||
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||
pollInteractionHandler: PollInteractionHandler(analyticsService: analytics, roomProxy: roomProxy),
|
||||
roomTimelineController: roomTimelineController)
|
||||
let coordinator = RoomPollsHistoryScreenCoordinator(parameters: parameters)
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .editPoll(let pollStartID, let poll):
|
||||
stateMachine.tryEvent(.presentPollForm(mode: .edit(eventID: pollStartID, poll: poll)))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissPollsHistory)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentRoomMemberDetails(member: RoomMemberProxyProtocol) {
|
||||
guard let roomProxy else {
|
||||
fatalError()
|
||||
@@ -1106,6 +1180,8 @@ private extension RoomFlowCoordinator {
|
||||
case messageForwarding(roomID: String, itemID: TimelineItemIdentifier)
|
||||
case reportContent(roomID: String, itemID: TimelineItemIdentifier, senderID: String)
|
||||
case pollForm(roomID: String)
|
||||
case pollsHistory(roomID: String)
|
||||
case pollsHistoryForm(roomID: String)
|
||||
}
|
||||
|
||||
struct EventUserInfo {
|
||||
@@ -1158,6 +1234,9 @@ private extension RoomFlowCoordinator {
|
||||
|
||||
case presentPollForm(mode: PollFormMode)
|
||||
case dismissPollForm
|
||||
|
||||
case presentPollsHistory
|
||||
case dismissPollsHistory
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1084,6 +1084,16 @@ public enum L10n {
|
||||
}
|
||||
/// Be in your element
|
||||
public static var screenOnboardingWelcomeTitle: String { return L10n.tr("Localizable", "screen_onboarding_welcome_title") }
|
||||
/// Can't find any ongoing polls.
|
||||
public static var screenPollsHistoryEmptyOngoing: String { return L10n.tr("Localizable", "screen_polls_history_empty_ongoing") }
|
||||
/// Can't find any past polls.
|
||||
public static var screenPollsHistoryEmptyPast: String { return L10n.tr("Localizable", "screen_polls_history_empty_past") }
|
||||
/// Ongoing
|
||||
public static var screenPollsHistoryFilterOngoing: String { return L10n.tr("Localizable", "screen_polls_history_filter_ongoing") }
|
||||
/// Past
|
||||
public static var screenPollsHistoryFilterPast: String { return L10n.tr("Localizable", "screen_polls_history_filter_past") }
|
||||
/// Polls
|
||||
public static var screenPollsHistoryTitle: String { return L10n.tr("Localizable", "screen_polls_history_title") }
|
||||
/// Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.
|
||||
public static var screenRecoveryKeyChangeDescription: String { return L10n.tr("Localizable", "screen_recovery_key_change_description") }
|
||||
/// Generate a new recovery key
|
||||
@@ -1626,6 +1636,11 @@ public enum L10n {
|
||||
public static var testLanguageIdentifier: String { return L10n.tr("Localizable", "test_language_identifier") }
|
||||
/// en
|
||||
public static var testUntranslatedDefaultLanguageIdentifier: String { return L10n.tr("Localizable", "test_untranslated_default_language_identifier") }
|
||||
|
||||
public enum Action {
|
||||
/// Load more
|
||||
public static var loadMore: String { return L10n.tr("Localizable", "action.load_more") }
|
||||
}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
@@ -1632,6 +1632,51 @@ class NotificationSettingsProxyMock: NotificationSettingsProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
class PollInteractionHandlerMock: PollInteractionHandlerProtocol {
|
||||
|
||||
//MARK: - sendPollResponse
|
||||
|
||||
var sendPollResponsePollStartIDOptionIDCallsCount = 0
|
||||
var sendPollResponsePollStartIDOptionIDCalled: Bool {
|
||||
return sendPollResponsePollStartIDOptionIDCallsCount > 0
|
||||
}
|
||||
var sendPollResponsePollStartIDOptionIDReceivedArguments: (pollStartID: String, optionID: String)?
|
||||
var sendPollResponsePollStartIDOptionIDReceivedInvocations: [(pollStartID: String, optionID: String)] = []
|
||||
var sendPollResponsePollStartIDOptionIDReturnValue: Result<Void, Error>!
|
||||
var sendPollResponsePollStartIDOptionIDClosure: ((String, String) async -> Result<Void, Error>)?
|
||||
|
||||
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> {
|
||||
sendPollResponsePollStartIDOptionIDCallsCount += 1
|
||||
sendPollResponsePollStartIDOptionIDReceivedArguments = (pollStartID: pollStartID, optionID: optionID)
|
||||
sendPollResponsePollStartIDOptionIDReceivedInvocations.append((pollStartID: pollStartID, optionID: optionID))
|
||||
if let sendPollResponsePollStartIDOptionIDClosure = sendPollResponsePollStartIDOptionIDClosure {
|
||||
return await sendPollResponsePollStartIDOptionIDClosure(pollStartID, optionID)
|
||||
} else {
|
||||
return sendPollResponsePollStartIDOptionIDReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - endPoll
|
||||
|
||||
var endPollPollStartIDCallsCount = 0
|
||||
var endPollPollStartIDCalled: Bool {
|
||||
return endPollPollStartIDCallsCount > 0
|
||||
}
|
||||
var endPollPollStartIDReceivedPollStartID: String?
|
||||
var endPollPollStartIDReceivedInvocations: [String] = []
|
||||
var endPollPollStartIDReturnValue: Result<Void, Error>!
|
||||
var endPollPollStartIDClosure: ((String) async -> Result<Void, Error>)?
|
||||
|
||||
func endPoll(pollStartID: String) async -> Result<Void, Error> {
|
||||
endPollPollStartIDCallsCount += 1
|
||||
endPollPollStartIDReceivedPollStartID = pollStartID
|
||||
endPollPollStartIDReceivedInvocations.append(pollStartID)
|
||||
if let endPollPollStartIDClosure = endPollPollStartIDClosure {
|
||||
return await endPollPollStartIDClosure(pollStartID)
|
||||
} else {
|
||||
return endPollPollStartIDReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class RoomMemberProxyMock: RoomMemberProxyProtocol {
|
||||
var userID: String {
|
||||
get { return underlyingUserID }
|
||||
@@ -2402,6 +2447,11 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
set(value) { underlyingTimelineProvider = value }
|
||||
}
|
||||
var underlyingTimelineProvider: RoomTimelineProviderProtocol!
|
||||
var timelineStartReached: Bool {
|
||||
get { return underlyingTimelineStartReached }
|
||||
set(value) { underlyingTimelineStartReached = value }
|
||||
}
|
||||
var underlyingTimelineStartReached: Bool!
|
||||
|
||||
//MARK: - subscribeForUpdates
|
||||
|
||||
@@ -2523,6 +2573,27 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
//MARK: - paginateBackwards
|
||||
|
||||
var paginateBackwardsRequestSizeCallsCount = 0
|
||||
var paginateBackwardsRequestSizeCalled: Bool {
|
||||
return paginateBackwardsRequestSizeCallsCount > 0
|
||||
}
|
||||
var paginateBackwardsRequestSizeReceivedRequestSize: UInt?
|
||||
var paginateBackwardsRequestSizeReceivedInvocations: [UInt] = []
|
||||
var paginateBackwardsRequestSizeReturnValue: Result<Void, TimelineProxyError>!
|
||||
var paginateBackwardsRequestSizeClosure: ((UInt) async -> Result<Void, TimelineProxyError>)?
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, TimelineProxyError> {
|
||||
paginateBackwardsRequestSizeCallsCount += 1
|
||||
paginateBackwardsRequestSizeReceivedRequestSize = requestSize
|
||||
paginateBackwardsRequestSizeReceivedInvocations.append(requestSize)
|
||||
if let paginateBackwardsRequestSizeClosure = paginateBackwardsRequestSizeClosure {
|
||||
return await paginateBackwardsRequestSizeClosure(requestSize)
|
||||
} else {
|
||||
return paginateBackwardsRequestSizeReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - paginateBackwards
|
||||
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount = 0
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsCalled: Bool {
|
||||
return paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount > 0
|
||||
|
||||
@@ -35,6 +35,7 @@ struct RoomProxyMockConfiguration {
|
||||
var timeline = {
|
||||
let mock = TimelineProxyMock()
|
||||
mock.underlyingActions = Empty(completeImmediately: false).eraseToAnyPublisher()
|
||||
mock.timelineStartReached = false
|
||||
return mock
|
||||
}()
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ enum A11yIdentifiers {
|
||||
static let notificationSettingsScreen = NotificationSettingsScreen()
|
||||
static let notificationSettingsEditScreen = NotificationSettingsEditScreen()
|
||||
static let pollFormScreen = PollFormScreen()
|
||||
static let roomPollsHistoryScreen = RoomPollsHistoryScreen()
|
||||
|
||||
struct AlertInfo {
|
||||
let primaryButton = "alert_info-primary_button"
|
||||
@@ -169,6 +170,7 @@ enum A11yIdentifiers {
|
||||
let people = "room_details-people"
|
||||
let invite = "room_details-invite"
|
||||
let notifications = "room_details-notifications"
|
||||
let pollsHistory = "romm_details-polls_history"
|
||||
}
|
||||
|
||||
struct RoomMemberDetailsScreen {
|
||||
@@ -263,4 +265,8 @@ enum A11yIdentifiers {
|
||||
"\(roomNamePrefix):\(name)"
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomPollsHistoryScreen {
|
||||
let loadMore = "room_polls_history_screen-load_more"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
||||
case presentRoomDetailsEditScreen(accountOwner: RoomMemberProxyProtocol)
|
||||
case presentNotificationSettingsScreen
|
||||
case presentInviteUsersScreen
|
||||
case presentPollsHistory
|
||||
}
|
||||
|
||||
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -71,6 +72,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentRoomDetailsEditScreen(accountOwner: accountOwner))
|
||||
case .requestNotificationSettingsPresentation:
|
||||
actionsSubject.send(.presentNotificationSettingsScreen)
|
||||
case .requestPollsHistoryPresentation:
|
||||
actionsSubject.send(.presentPollsHistory)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -27,6 +27,7 @@ enum RoomDetailsScreenViewModelAction {
|
||||
case requestInvitePeoplePresentation
|
||||
case leftRoom
|
||||
case requestEditDetailsPresentation(RoomMemberProxyProtocol)
|
||||
case requestPollsHistoryPresentation
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
@@ -174,6 +175,7 @@ enum RoomDetailsScreenViewAction {
|
||||
case processTapNotifications
|
||||
case processToogleMuteNotifications
|
||||
case displayAvatar
|
||||
case processTapPolls
|
||||
}
|
||||
|
||||
enum RoomDetailsScreenViewShortcut {
|
||||
|
||||
@@ -67,7 +67,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
notificationSettingsState: .loading,
|
||||
bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
|
||||
|
||||
setupRoomSubscription()
|
||||
fetchMembers()
|
||||
|
||||
@@ -120,6 +120,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
Task { await toggleMuteNotifications() }
|
||||
case .displayAvatar:
|
||||
displayFullScreenAvatar()
|
||||
case .processTapPolls:
|
||||
actionsSubject.send(.requestPollsHistoryPresentation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,7 @@ struct RoomDetailsScreen: View {
|
||||
|
||||
notificationSection
|
||||
|
||||
if context.viewState.dmRecipient == nil {
|
||||
aboutSection
|
||||
}
|
||||
aboutSection
|
||||
|
||||
securitySection
|
||||
|
||||
@@ -150,22 +148,30 @@ struct RoomDetailsScreen: View {
|
||||
|
||||
private var aboutSection: some View {
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.commonPeople,
|
||||
icon: CompoundIcon(asset: Asset.Images.user)),
|
||||
details: .title(String(context.viewState.joinedMembersCount)),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapPeople)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.people)
|
||||
|
||||
if context.viewState.canInviteUsers {
|
||||
ListRow(label: .default(title: L10n.screenRoomDetailsInvitePeopleTitle,
|
||||
icon: CompoundIcon(asset: Asset.Images.userAdd)),
|
||||
if context.viewState.dmRecipient == nil {
|
||||
ListRow(label: .default(title: L10n.commonPeople,
|
||||
icon: CompoundIcon(asset: Asset.Images.user)),
|
||||
details: .title(String(context.viewState.joinedMembersCount)),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapInvite)
|
||||
context.send(viewAction: .processTapPeople)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.invite)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.people)
|
||||
|
||||
if context.viewState.canInviteUsers {
|
||||
ListRow(label: .default(title: L10n.screenRoomDetailsInvitePeopleTitle,
|
||||
icon: CompoundIcon(asset: Asset.Images.userAdd)),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapInvite)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.invite)
|
||||
}
|
||||
}
|
||||
ListRow(label: .default(title: L10n.screenPollsHistoryTitle,
|
||||
icon: CompoundIcon(asset: Asset.Images.polls)),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapPolls)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.pollsHistory)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// 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 RoomPollsHistoryScreenCoordinatorParameters {
|
||||
let roomProxy: RoomProxyProtocol
|
||||
let pollInteractionHandler: PollInteractionHandlerProtocol
|
||||
let roomTimelineController: RoomTimelineControllerProtocol
|
||||
}
|
||||
|
||||
enum RoomPollsHistoryScreenCoordinatorAction {
|
||||
case editPoll(pollStartID: String, poll: Poll)
|
||||
}
|
||||
|
||||
final class RoomPollsHistoryScreenCoordinator: CoordinatorProtocol {
|
||||
private var viewModel: RoomPollsHistoryScreenViewModelProtocol
|
||||
private let actionsSubject: PassthroughSubject<RoomPollsHistoryScreenCoordinatorAction, Never> = .init()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
var actions: AnyPublisher<RoomPollsHistoryScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: RoomPollsHistoryScreenCoordinatorParameters) {
|
||||
viewModel = RoomPollsHistoryScreenViewModel(roomProxy: parameters.roomProxy,
|
||||
pollInteractionHandler: parameters.pollInteractionHandler,
|
||||
roomTimelineController: parameters.roomTimelineController,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .editPoll(let pollStartID, let poll):
|
||||
actionsSubject.send(.editPoll(pollStartID: pollStartID, poll: poll))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(RoomPollsHistoryScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// 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 RoomPollsHistoryScreenViewModelAction {
|
||||
case editPoll(pollStartID: String, poll: Poll)
|
||||
}
|
||||
|
||||
struct RoomPollsHistoryScreenViewState: BindableState {
|
||||
let title: String
|
||||
let filters: [RoomPollsHistoryFilter] = [.ongoing, .past]
|
||||
var pollTimelineItems: [RoomPollsHistoryPollDetails] = []
|
||||
var canBackPaginate = false
|
||||
var isBackPaginating = false
|
||||
var bindings: RoomPollsHistoryScreenViewStateBindings
|
||||
|
||||
var emptyStateMessage: String {
|
||||
switch bindings.filter {
|
||||
case .ongoing:
|
||||
L10n.screenPollsHistoryEmptyOngoing
|
||||
case .past:
|
||||
L10n.screenPollsHistoryEmptyPast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomPollsHistoryScreenViewStateBindings {
|
||||
/// Polls list filter
|
||||
var filter: RoomPollsHistoryFilter
|
||||
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomPollsHistoryScreenErrorType>?
|
||||
}
|
||||
|
||||
enum RoomPollsHistoryScreenViewAction {
|
||||
case filter(RoomPollsHistoryFilter)
|
||||
case end(pollStartID: String)
|
||||
case edit(pollStartID: String, poll: Poll)
|
||||
case sendPollResponse(pollStartID: String, optionID: String)
|
||||
case loadMore
|
||||
}
|
||||
|
||||
enum RoomPollsHistoryFilter: Equatable {
|
||||
case ongoing
|
||||
case past
|
||||
}
|
||||
|
||||
struct RoomPollsHistoryPollDetails {
|
||||
let timestamp: Date
|
||||
let item: PollRoomTimelineItem
|
||||
}
|
||||
|
||||
extension RoomPollsHistoryFilter: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .ongoing:
|
||||
L10n.screenPollsHistoryFilterOngoing
|
||||
case .past:
|
||||
L10n.screenPollsHistoryFilterPast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomPollsHistoryScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// 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 Algorithms
|
||||
import Combine
|
||||
import OrderedCollections
|
||||
import SwiftUI
|
||||
|
||||
typealias RoomPollsHistoryScreenViewModelType = StateStoreViewModel<RoomPollsHistoryScreenViewState, RoomPollsHistoryScreenViewAction>
|
||||
|
||||
class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, RoomPollsHistoryScreenViewModelProtocol {
|
||||
private enum Constants {
|
||||
static let backPaginationEventLimit: UInt = 250
|
||||
}
|
||||
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private let pollInteractionHandler: PollInteractionHandlerProtocol
|
||||
private let roomTimelineController: RoomTimelineControllerProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private var paginateBackwardsTask: Task<Void, Never>?
|
||||
private let isPaginatingIndicatorID = UUID().uuidString
|
||||
|
||||
private var actionsSubject: PassthroughSubject<RoomPollsHistoryScreenViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<RoomPollsHistoryScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(roomProxy: RoomProxyProtocol,
|
||||
pollInteractionHandler: PollInteractionHandlerProtocol,
|
||||
roomTimelineController: RoomTimelineControllerProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
self.pollInteractionHandler = pollInteractionHandler
|
||||
self.roomTimelineController = roomTimelineController
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
super.init(initialViewState: RoomPollsHistoryScreenViewState(title: L10n.screenPollsHistoryTitle,
|
||||
canBackPaginate: true,
|
||||
bindings: .init(filter: .ongoing)))
|
||||
|
||||
setupSubscriptions()
|
||||
updatePollsList(filter: state.bindings.filter)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: RoomPollsHistoryScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .edit(let pollStartID, let poll):
|
||||
actionsSubject.send(.editPoll(pollStartID: pollStartID, poll: poll))
|
||||
case .end(let pollStartID):
|
||||
endPoll(pollStartID: pollStartID)
|
||||
case .filter(let filter):
|
||||
updatePollsList(filter: filter)
|
||||
case .loadMore:
|
||||
paginateBackwards()
|
||||
case .sendPollResponse(let pollStartID, let optionID):
|
||||
sendPollResponse(pollStartID: pollStartID, optionID: optionID)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupSubscriptions() {
|
||||
roomTimelineController.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
guard let self else { return }
|
||||
|
||||
switch callback {
|
||||
case .updatedTimelineItems:
|
||||
self.updatePollsList(filter: state.bindings.filter)
|
||||
case .canBackPaginate(let canBackPaginate):
|
||||
if self.state.canBackPaginate != canBackPaginate {
|
||||
self.state.canBackPaginate = canBackPaginate
|
||||
}
|
||||
case .isBackPaginating:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
context.$viewState
|
||||
.map(\.isBackPaginating)
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] isBackPaginating in
|
||||
guard let self else { return }
|
||||
if isBackPaginating {
|
||||
userIndicatorController.submitIndicator(.init(id: isPaginatingIndicatorID, type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false), title: L10n.commonLoading))
|
||||
} else {
|
||||
userIndicatorController.retractIndicatorWithId(isPaginatingIndicatorID)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func displayError(message: String) {
|
||||
state.bindings.alertInfo = .init(id: .alert, title: message)
|
||||
}
|
||||
|
||||
// MARK: - Poll Interaction Handler
|
||||
|
||||
private func endPoll(pollStartID: String) {
|
||||
Task {
|
||||
do {
|
||||
try await pollInteractionHandler.endPoll(pollStartID: pollStartID).get()
|
||||
} catch {
|
||||
MXLog.error("Failed to end poll. \(error)")
|
||||
displayError(message: L10n.errorUnknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sendPollResponse(pollStartID: String, optionID: String) {
|
||||
Task {
|
||||
do {
|
||||
try await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID).get()
|
||||
} catch {
|
||||
MXLog.error("Failed to send poll response. \(error)")
|
||||
displayError(message: L10n.errorUnknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timeline
|
||||
|
||||
private func updatePollsList(filter: RoomPollsHistoryFilter) {
|
||||
// Get the poll timeline items to display
|
||||
var items: [PollRoomTimelineItem] = []
|
||||
for timelineItem in roomTimelineController.timelineItems {
|
||||
if let pollRoomTimelineItem = timelineItem as? PollRoomTimelineItem {
|
||||
// Apply the filter
|
||||
switch filter {
|
||||
case .ongoing where !pollRoomTimelineItem.poll.hasEnded:
|
||||
items.append(pollRoomTimelineItem)
|
||||
case .past where pollRoomTimelineItem.poll.hasEnded:
|
||||
items.append(pollRoomTimelineItem)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map into RoomPollsHistoryPollDetails to have both the event timestamp and the timeline item
|
||||
state.pollTimelineItems = items.map { item in
|
||||
guard let timestamp = roomTimelineController.eventTimestamp(for: item.id) else {
|
||||
return nil
|
||||
}
|
||||
return RoomPollsHistoryPollDetails(timestamp: timestamp, item: item)
|
||||
}
|
||||
.compactMap { $0 }
|
||||
.sorted { $0.timestamp > $1.timestamp }
|
||||
}
|
||||
|
||||
private func paginateBackwards() {
|
||||
guard paginateBackwardsTask == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
paginateBackwardsTask = Task { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
state.isBackPaginating = true
|
||||
switch await roomTimelineController.paginateBackwards(requestSize: Constants.backPaginationEventLimit) {
|
||||
case .failure(let error):
|
||||
MXLog.error("failed to back paginate. \(error)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
paginateBackwardsTask = nil
|
||||
state.isBackPaginating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
extension RoomPollsHistoryScreenViewModel {
|
||||
static let mock = RoomPollsHistoryScreenViewModel(roomProxy: RoomProxyMock(),
|
||||
pollInteractionHandler: PollInteractionHandlerMock(),
|
||||
roomTimelineController: MockRoomTimelineController(),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@MainActor
|
||||
protocol RoomPollsHistoryScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<RoomPollsHistoryScreenViewModelAction, Never> { get }
|
||||
var context: RoomPollsHistoryScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// 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 Compound
|
||||
import SwiftUI
|
||||
|
||||
struct RoomPollsHistoryScreen: View {
|
||||
@ObservedObject var context: RoomPollsHistoryScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .center, spacing: 16) {
|
||||
modePicker
|
||||
|
||||
polls
|
||||
|
||||
if context.viewState.pollTimelineItems.isEmpty {
|
||||
emptyStateMessage
|
||||
.padding(.top, 48)
|
||||
}
|
||||
|
||||
if context.viewState.canBackPaginate {
|
||||
loadMoreButton
|
||||
.padding(.top, context.viewState.pollTimelineItems.isEmpty ? 0 : 16)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.alert(item: $context.alertInfo)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(.compound.bgSubtleSecondaryLevel0)
|
||||
.navigationTitle(context.viewState.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var modePicker: some View {
|
||||
Picker("", selection: $context.filter) {
|
||||
ForEach(context.viewState.filters, id: \.self) { filter in
|
||||
Text(filter.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.readableFrame(maxWidth: 475)
|
||||
.onChange(of: context.filter) { value in
|
||||
context.send(viewAction: .filter(value))
|
||||
}
|
||||
}
|
||||
|
||||
private var polls: some View {
|
||||
ForEach(context.viewState.pollTimelineItems, id: \.item.id.eventID) { pollTimelineItem in
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(DateFormatter.pollTimestamp.string(from: pollTimelineItem.timestamp))
|
||||
.font(.compound.bodySM)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
PollView(poll: pollTimelineItem.item.poll, editable: pollTimelineItem.item.isEditable) { action in
|
||||
switch action {
|
||||
case .selectOption(let optionID):
|
||||
guard let pollStartID = pollTimelineItem.item.id.eventID else { return }
|
||||
context.send(viewAction: .sendPollResponse(pollStartID: pollStartID, optionID: optionID))
|
||||
case .edit:
|
||||
guard let pollStartID = pollTimelineItem.item.id.eventID else { return }
|
||||
context.send(viewAction: .edit(pollStartID: pollStartID, poll: pollTimelineItem.item.poll))
|
||||
case .end:
|
||||
guard let pollStartID = pollTimelineItem.item.id.eventID else { return }
|
||||
context.send(viewAction: .end(pollStartID: pollStartID))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.init(top: 12, leading: 12, bottom: 12, trailing: 12))
|
||||
.background(.compound.bgCanvasDefaultLevel1)
|
||||
.cornerRadius(12, corners: .allCorners)
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyStateMessage: some View {
|
||||
Text(context.viewState.emptyStateMessage)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
|
||||
private var loadMoreButton: some View {
|
||||
Button {
|
||||
context.send(viewAction: .loadMore)
|
||||
} label: {
|
||||
Text(L10n.Action.loadMore)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomPollsHistoryScreen.loadMore)
|
||||
.buttonStyle(.compound(.secondary))
|
||||
.fixedSize()
|
||||
.disabled(context.viewState.isBackPaginating)
|
||||
}
|
||||
}
|
||||
|
||||
private extension DateFormatter {
|
||||
static let pollTimestamp: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .short
|
||||
return dateFormatter
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct RoomPollsHistoryScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModelEmpty: RoomPollsHistoryScreenViewModel = {
|
||||
let roomTimelineController = MockRoomTimelineController()
|
||||
roomTimelineController.timelineItems = []
|
||||
let roomProxyMockConfiguration = RoomProxyMockConfiguration(displayName: "Polls")
|
||||
roomProxyMockConfiguration.timeline.timelineStartReached = false
|
||||
let viewModel = RoomPollsHistoryScreenViewModel(roomProxy: RoomProxyMock(with: roomProxyMockConfiguration),
|
||||
pollInteractionHandler: PollInteractionHandlerMock(),
|
||||
roomTimelineController: roomTimelineController,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
return viewModel
|
||||
}()
|
||||
|
||||
static let viewModel: RoomPollsHistoryScreenViewModel = {
|
||||
let roomTimelineController = MockRoomTimelineController()
|
||||
|
||||
let polls = [PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
|
||||
PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true)]
|
||||
|
||||
roomTimelineController.timelineItems = polls
|
||||
|
||||
for i in 0..<polls.count {
|
||||
let item = polls[i]
|
||||
let date: Date! = DateComponents(calendar: .current, timeZone: .gmt, year: 2023, month: 12, day: 1 + i, hour: 12).date
|
||||
roomTimelineController.timelineItemsTimestamp[item.id] = date
|
||||
}
|
||||
|
||||
let roomProxyMockConfiguration = RoomProxyMockConfiguration(displayName: "Polls")
|
||||
roomProxyMockConfiguration.timeline.timelineStartReached = true
|
||||
let viewModel = RoomPollsHistoryScreenViewModel(roomProxy: RoomProxyMock(with: roomProxyMockConfiguration),
|
||||
pollInteractionHandler: PollInteractionHandlerMock(),
|
||||
roomTimelineController: roomTimelineController,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
|
||||
return viewModel
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
RoomPollsHistoryScreen(context: viewModelEmpty.context)
|
||||
}
|
||||
.previewDisplayName("No polls")
|
||||
.snapshot(delay: 1.0)
|
||||
|
||||
NavigationStack {
|
||||
RoomPollsHistoryScreen(context: viewModel.context)
|
||||
}
|
||||
.previewDisplayName("polls")
|
||||
.snapshot(delay: 1.0)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ class RoomScreenInteractionHandler {
|
||||
private let application: ApplicationProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analyticsService: AnalyticsService
|
||||
private let pollInteractionHandler: PollInteractionHandlerProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<RoomScreenInteractionHandlerAction, Never> = .init()
|
||||
var actions: AnyPublisher<RoomScreenInteractionHandlerAction, Never> {
|
||||
@@ -73,6 +74,7 @@ class RoomScreenInteractionHandler {
|
||||
self.application = application
|
||||
self.appSettings = appSettings
|
||||
self.analyticsService = analyticsService
|
||||
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
||||
}
|
||||
|
||||
// MARK: Timeline Item Action Menu
|
||||
@@ -274,9 +276,8 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
func sendPollResponse(pollStartID: String, optionID: String) {
|
||||
Task {
|
||||
let sendPollResponseResult = await roomProxy.timeline.sendPollResponse(pollStartID: pollStartID, answers: [optionID])
|
||||
analyticsService.trackPollVote()
|
||||
|
||||
let sendPollResponseResult = await pollInteractionHandler.sendPollResponse(pollStartID: pollStartID, optionID: optionID)
|
||||
|
||||
switch sendPollResponseResult {
|
||||
case .success:
|
||||
break
|
||||
@@ -288,9 +289,8 @@ class RoomScreenInteractionHandler {
|
||||
|
||||
func endPoll(pollStartID: String) {
|
||||
Task {
|
||||
let endPollResult = await roomProxy.timeline.endPoll(pollStartID: pollStartID,
|
||||
text: "The poll with event id: \(pollStartID) has ended")
|
||||
analyticsService.trackPollEnd()
|
||||
let endPollResult = await pollInteractionHandler.endPoll(pollStartID: pollStartID)
|
||||
|
||||
switch endPollResult {
|
||||
case .success:
|
||||
break
|
||||
|
||||
174
ElementX/Sources/Screens/RoomScreen/View/Polls/PollView.swift
Normal file
174
ElementX/Sources/Screens/RoomScreen/View/Polls/PollView.swift
Normal file
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
enum PollViewAction {
|
||||
case selectOption(optionID: String)
|
||||
case edit
|
||||
case end
|
||||
}
|
||||
|
||||
struct PollView: View {
|
||||
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
||||
|
||||
let poll: Poll
|
||||
let editable: Bool
|
||||
let actionHandler: (PollViewAction) -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
questionView
|
||||
optionsView
|
||||
summaryView
|
||||
toolbarView
|
||||
}
|
||||
.frame(maxWidth: 450)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var questionView: some View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
let asset = poll.hasEnded ? Asset.Images.pollsEnd : Asset.Images.polls
|
||||
|
||||
Image(asset.name)
|
||||
.resizable()
|
||||
.scaledFrame(size: 22)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(poll.question)
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
}
|
||||
}
|
||||
|
||||
private var optionsView: some View {
|
||||
ForEach(poll.options, id: \.id) { option in
|
||||
Button {
|
||||
guard !option.isSelected else { return }
|
||||
actionHandler(.selectOption(optionID: option.id))
|
||||
feedbackGenerator.impactOccurred()
|
||||
} label: {
|
||||
PollOptionView(pollOption: option,
|
||||
showVotes: showVotes,
|
||||
isFinalResult: poll.hasEnded)
|
||||
.foregroundColor(progressBarColor(for: option))
|
||||
}
|
||||
.disabled(poll.hasEnded)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var summaryView: some View {
|
||||
if let summaryText = poll.summaryText {
|
||||
Text(summaryText)
|
||||
.font(.compound.bodySM)
|
||||
.scaledPadding(.leading, showVotes ? 0 : 32)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.frame(maxWidth: .infinity, alignment: showVotes ? .trailing : .leading)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var toolbarView: some View {
|
||||
if !poll.hasEnded, poll.createdByAccountOwner {
|
||||
Button {
|
||||
toolbarAction()
|
||||
} label: {
|
||||
Text(editable ? L10n.actionEditPoll : L10n.actionEndPoll)
|
||||
.lineLimit(2, reservesSpace: false)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textOnSolidPrimary)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 10)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background {
|
||||
Capsule()
|
||||
.foregroundColor(.compound.bgActionPrimaryRest)
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private func toolbarAction() {
|
||||
if editable {
|
||||
actionHandler(.edit)
|
||||
} else {
|
||||
actionHandler(.end)
|
||||
}
|
||||
}
|
||||
|
||||
private func progressBarColor(for option: Poll.Option) -> Color {
|
||||
if poll.hasEnded {
|
||||
return option.isWinning ? .compound.textActionAccent : .compound.textDisabled
|
||||
} else {
|
||||
return .compound.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
private var showVotes: Bool {
|
||||
poll.hasEnded || poll.kind == .disclosed
|
||||
}
|
||||
}
|
||||
|
||||
private extension Poll {
|
||||
var summaryText: String? {
|
||||
guard !hasEnded else {
|
||||
return options.first.map {
|
||||
L10n.commonPollTotalVotes($0.allVotes)
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case .disclosed:
|
||||
return options.first.map {
|
||||
L10n.commonPollTotalVotes($0.allVotes)
|
||||
}
|
||||
case .undisclosed:
|
||||
return L10n.commonPollUndisclosedText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PollView_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
PollView(poll: .disclosed(), editable: false) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Disclosed")
|
||||
|
||||
PollView(poll: .undisclosed(), editable: false) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Undisclosed")
|
||||
|
||||
PollView(poll: .endedDisclosed, editable: false) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Ended, Disclosed")
|
||||
|
||||
PollView(poll: .endedUndisclosed, editable: false) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Ended, Undisclosed")
|
||||
|
||||
PollView(poll: .disclosed(createdByAccountOwner: true), editable: true) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Creator, disclosed")
|
||||
|
||||
PollView(poll: .emptyDisclosed, editable: true) { _ in }
|
||||
.padding()
|
||||
.previewDisplayName("Creator, no votes")
|
||||
}
|
||||
}
|
||||
@@ -19,136 +19,35 @@ import SwiftUI
|
||||
struct PollRoomTimelineView: View {
|
||||
let timelineItem: PollRoomTimelineItem
|
||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||
|
||||
|
||||
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
||||
|
||||
|
||||
var body: some View {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
questionView
|
||||
optionsView
|
||||
summaryView
|
||||
toolbarView
|
||||
PollView(poll: poll, editable: timelineItem.isEditable) { action in
|
||||
switch action {
|
||||
case .selectOption(let optionID):
|
||||
guard let eventID, let option = poll.options.first(where: { $0.id == optionID }), !option.isSelected else { return }
|
||||
context.send(viewAction: .poll(.selectOption(pollStartID: eventID, optionID: option.id)))
|
||||
case .edit:
|
||||
guard let eventID else { return }
|
||||
context.send(viewAction: .poll(.edit(pollStartID: eventID, poll: poll)))
|
||||
case .end:
|
||||
guard let eventID else { return }
|
||||
context.send(viewAction: .poll(.end(pollStartID: eventID)))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 450)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var poll: Poll {
|
||||
timelineItem.poll
|
||||
}
|
||||
|
||||
private var eventID: String? {
|
||||
timelineItem.id.eventID
|
||||
}
|
||||
|
||||
private var questionView: some View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
let asset = poll.hasEnded ? Asset.Images.pollsEnd : Asset.Images.polls
|
||||
|
||||
Image(asset.name)
|
||||
.resizable()
|
||||
.scaledFrame(size: 22)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(poll.question)
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
}
|
||||
}
|
||||
|
||||
private var optionsView: some View {
|
||||
ForEach(poll.options, id: \.id) { option in
|
||||
Button {
|
||||
guard let eventID, !option.isSelected else { return }
|
||||
context.send(viewAction: .poll(.selectOption(pollStartID: eventID, optionID: option.id)))
|
||||
feedbackGenerator.impactOccurred()
|
||||
} label: {
|
||||
PollOptionView(pollOption: option,
|
||||
showVotes: showVotes,
|
||||
isFinalResult: poll.hasEnded)
|
||||
.foregroundColor(progressBarColor(for: option))
|
||||
}
|
||||
.disabled(poll.hasEnded || eventID == nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var summaryView: some View {
|
||||
if let summaryText = poll.summaryText {
|
||||
Text(summaryText)
|
||||
.font(.compound.bodySM)
|
||||
.scaledPadding(.leading, showVotes ? 0 : 32)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.frame(maxWidth: .infinity, alignment: showVotes ? .trailing : .leading)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var toolbarView: some View {
|
||||
if !poll.hasEnded, poll.createdByAccountOwner {
|
||||
Button {
|
||||
toolbarAction()
|
||||
} label: {
|
||||
Text(timelineItem.isEditable ? L10n.actionEditPoll : L10n.actionEndPoll)
|
||||
.lineLimit(2, reservesSpace: false)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textOnSolidPrimary)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 10)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background {
|
||||
Capsule()
|
||||
.foregroundColor(.compound.bgActionPrimaryRest)
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private func toolbarAction() {
|
||||
guard let eventID else {
|
||||
return
|
||||
}
|
||||
|
||||
if timelineItem.isEditable {
|
||||
context.send(viewAction: .poll(.edit(pollStartID: eventID, poll: poll)))
|
||||
} else {
|
||||
context.send(viewAction: .poll(.end(pollStartID: eventID)))
|
||||
}
|
||||
// MARK: - Private
|
||||
|
||||
private var poll: Poll {
|
||||
timelineItem.poll
|
||||
}
|
||||
|
||||
private func progressBarColor(for option: Poll.Option) -> Color {
|
||||
if poll.hasEnded {
|
||||
return option.isWinning ? .compound.textActionAccent : .compound.textDisabled
|
||||
} else {
|
||||
return .compound.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
private var showVotes: Bool {
|
||||
poll.hasEnded || poll.kind == .disclosed
|
||||
}
|
||||
}
|
||||
|
||||
private extension Poll {
|
||||
var summaryText: String? {
|
||||
guard !hasEnded else {
|
||||
return options.first.map {
|
||||
L10n.commonPollTotalVotes($0.allVotes)
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case .disclosed:
|
||||
return options.first.map {
|
||||
L10n.commonPollTotalVotes($0.allVotes)
|
||||
}
|
||||
case .undisclosed:
|
||||
return L10n.commonPollUndisclosedText
|
||||
}
|
||||
|
||||
private var eventID: String? {
|
||||
timelineItem.id.eventID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ struct DeveloperOptionsScreen: View {
|
||||
Text("Use encryption")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
Button {
|
||||
showConfetti = true
|
||||
|
||||
41
ElementX/Sources/Services/Polls/PollInteractionHandler.swift
Normal file
41
ElementX/Sources/Services/Polls/PollInteractionHandler.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
class PollInteractionHandler: PollInteractionHandlerProtocol {
|
||||
let analyticsService: AnalyticsService
|
||||
let roomProxy: RoomProxyProtocol
|
||||
|
||||
init(analyticsService: AnalyticsService, roomProxy: RoomProxyProtocol) {
|
||||
self.analyticsService = analyticsService
|
||||
self.roomProxy = roomProxy
|
||||
}
|
||||
|
||||
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> {
|
||||
let sendPollResponseResult = await roomProxy.timeline.sendPollResponse(pollStartID: pollStartID, answers: [optionID])
|
||||
analyticsService.trackPollVote()
|
||||
|
||||
return sendPollResponseResult.mapError { $0 }
|
||||
}
|
||||
|
||||
func endPoll(pollStartID: String) async -> Result<Void, Error> {
|
||||
let endPollResult = await roomProxy.timeline.endPoll(pollStartID: pollStartID,
|
||||
text: "The poll with event id: \(pollStartID) has ended")
|
||||
analyticsService.trackPollEnd()
|
||||
return endPollResult.mapError { $0 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
protocol PollInteractionHandlerProtocol {
|
||||
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error>
|
||||
func endPoll(pollStartID: String) async -> Result<Void, Error>
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
extension PollInteractionHandlerProtocol { }
|
||||
@@ -31,6 +31,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||
|
||||
var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default
|
||||
var timelineItemsTimestamp: [TimelineItemIdentifier: Date] = [:]
|
||||
|
||||
private var client: UITestsSignalling.Client?
|
||||
|
||||
@@ -43,7 +44,12 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
fatalError("Failure setting up signalling: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
try? await simulateBackPagination()
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
callbacks.send(.canBackPaginate(false))
|
||||
return .success(())
|
||||
@@ -99,6 +105,10 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
await roomProxy?.timeline.cancelSend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
|
||||
timelineItemsTimestamp[itemID] ?? .now
|
||||
}
|
||||
|
||||
// MARK: - UI Test signalling
|
||||
|
||||
/// The cancellable used for UI Tests signalling.
|
||||
@@ -153,6 +163,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
timelineItems.insert(contentsOf: newItems, at: 0)
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
callbacks.send(.isBackPaginating(false))
|
||||
callbacks.send(.canBackPaginate(!backPaginationResponses.isEmpty))
|
||||
|
||||
try client?.send(.success)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
MXLog.info("Started back pagination request")
|
||||
switch await roomProxy.timeline.paginateBackwards(requestSize: requestSize) {
|
||||
case .success:
|
||||
MXLog.info("Finished back pagination request")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed back pagination request with error: \(error)")
|
||||
return .failure(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
MXLog.info("Started back pagination request")
|
||||
switch await roomProxy.timeline.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
|
||||
@@ -236,7 +248,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
private func updateTimelineItems() {
|
||||
var newTimelineItems = [RoomTimelineItemProtocol]()
|
||||
var canBackPaginate = true
|
||||
var canBackPaginate = !roomProxy.timeline.timelineStartReached
|
||||
var isBackPaginating = false
|
||||
var lastEncryptedHistoryItemIndex: Int?
|
||||
|
||||
@@ -299,7 +311,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
timelineItems = newTimelineItems
|
||||
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
@@ -384,4 +396,20 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
|
||||
for itemProxy in roomProxy.timeline.timelineProvider.itemProxies {
|
||||
switch itemProxy {
|
||||
case .event(let eventTimelineItemProxy):
|
||||
if eventTimelineItemProxy.id == itemID {
|
||||
return eventTimelineItemProxy.timestamp
|
||||
}
|
||||
case .virtual:
|
||||
break
|
||||
case .unknown:
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ protocol RoomTimelineControllerProtocol {
|
||||
|
||||
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError>
|
||||
@@ -70,6 +72,8 @@ protocol RoomTimelineControllerProtocol {
|
||||
func retrySending(itemID: TimelineItemIdentifier) async
|
||||
|
||||
func cancelSending(itemID: TimelineItemIdentifier) async
|
||||
|
||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date?
|
||||
}
|
||||
|
||||
extension RoomTimelineControllerProtocol {
|
||||
|
||||
@@ -34,6 +34,8 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
private let backPaginationStateSubject = PassthroughSubject<BackPaginationStatus, Never>()
|
||||
private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>()
|
||||
|
||||
private(set) var timelineStartReached = false
|
||||
|
||||
private let actionsSubject = PassthroughSubject<TimelineProxyAction, Never>()
|
||||
var actions: AnyPublisher<TimelineProxyAction, Never> {
|
||||
@@ -134,6 +136,18 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
try? timeline.getTimelineEventContentByEventId(eventId: eventID)
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, TimelineProxyError> {
|
||||
do {
|
||||
try await Task.dispatch(on: .global()) {
|
||||
try self.timeline.paginateBackwards(opts: .simpleRequest(eventLimit: UInt16(requestSize), waitForToken: true))
|
||||
}
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError> {
|
||||
do {
|
||||
try await Task.dispatch(on: .global()) {
|
||||
@@ -480,6 +494,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
private func subscribeToBackpagination() {
|
||||
let listener = RoomBackpaginationStatusListener { [weak self] status in
|
||||
if status == .timelineStartReached {
|
||||
self?.timelineStartReached = true
|
||||
}
|
||||
self?.backPaginationStateSubject.send(status)
|
||||
}
|
||||
do {
|
||||
|
||||
@@ -43,6 +43,8 @@ protocol TimelineProxyProtocol {
|
||||
|
||||
var timelineProvider: RoomTimelineProviderProtocol { get }
|
||||
|
||||
var timelineStartReached: Bool { get }
|
||||
|
||||
func subscribeForUpdates() async
|
||||
|
||||
/// Cancels a failed message given its transaction ID from the timeline
|
||||
@@ -62,6 +64,8 @@ protocol TimelineProxyProtocol {
|
||||
/// Retries sending a failed message given its transaction ID
|
||||
func retrySend(transactionID: String) async
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func sendAudio(url: URL,
|
||||
|
||||
@@ -877,6 +877,40 @@ class MockScreen: Identifiable {
|
||||
let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomPollsHistoryEmptyLoadMore:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let interactionHandler = PollInteractionHandlerMock()
|
||||
let roomTimelineController = MockRoomTimelineController()
|
||||
roomTimelineController.backPaginationResponses = [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
let roomProxyMockConfiguration = RoomProxyMockConfiguration(displayName: "Polls")
|
||||
roomProxyMockConfiguration.timeline.timelineStartReached = false
|
||||
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: roomProxyMockConfiguration),
|
||||
pollInteractionHandler: interactionHandler,
|
||||
roomTimelineController: roomTimelineController)
|
||||
let coordinator = RoomPollsHistoryScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomPollsHistoryLoadMore:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let interactionHandler = PollInteractionHandlerMock()
|
||||
let roomTimelineController = MockRoomTimelineController()
|
||||
|
||||
let poll = PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true)
|
||||
roomTimelineController.timelineItems = [poll]
|
||||
let date: Date! = DateComponents(calendar: .current, timeZone: .gmt, year: 2023, month: 12, day: 1, hour: 12).date
|
||||
roomTimelineController.timelineItemsTimestamp = [poll.id: date]
|
||||
|
||||
let roomProxyMockConfiguration = RoomProxyMockConfiguration(displayName: "Polls")
|
||||
roomProxyMockConfiguration.timeline.timelineStartReached = false
|
||||
let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: roomProxyMockConfiguration),
|
||||
pollInteractionHandler: interactionHandler,
|
||||
roomTimelineController: roomTimelineController)
|
||||
let coordinator = RoomPollsHistoryScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ enum UITestsScreenIdentifier: String {
|
||||
case createRoom
|
||||
case createRoomNoUsers
|
||||
case createPoll
|
||||
case roomPollsHistoryEmptyLoadMore
|
||||
case roomPollsHistoryLoadMore
|
||||
}
|
||||
|
||||
extension UITestsScreenIdentifier: CustomStringConvertible {
|
||||
|
||||
36
UITests/Sources/RoomPollsHistoryScreenUITests.swift
Normal file
36
UITests/Sources/RoomPollsHistoryScreenUITests.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
|
||||
@MainActor
|
||||
class RoomPollsHistoryScreenUITests: XCTestCase {
|
||||
func testEmptyPollsHistory() async throws {
|
||||
let app = Application.launch(.roomPollsHistoryEmptyLoadMore)
|
||||
|
||||
XCTAssert(app.buttons[A11yIdentifiers.roomPollsHistoryScreen.loadMore].waitForExistence(timeout: 1))
|
||||
|
||||
try await app.assertScreenshot(.roomPollsHistoryEmptyLoadMore)
|
||||
}
|
||||
|
||||
func testPollsHistory() async throws {
|
||||
let app = Application.launch(.roomPollsHistoryLoadMore)
|
||||
|
||||
XCTAssert(app.buttons[A11yIdentifiers.roomPollsHistoryScreen.loadMore].waitForExistence(timeout: 1))
|
||||
|
||||
try await app.assertScreenshot(.roomPollsHistoryLoadMore)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b0266cfccbe6a640908b54c226e1e2d544bedd1aa3343304163fc87f54c9d437
|
||||
size 105010
|
||||
oid sha256:e0f0d0153b192c9d39095958380d457e2cf2857fb7de3e385c48dba4b15219d2
|
||||
size 108327
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:013cb46ed50d1a9c302218afbac4aec5a99c76876915dbe83e54354f444aedd3
|
||||
size 134604
|
||||
oid sha256:ccb8342762f048022b0cb9b2100d235e120e56e485a7564c0bd572a807398bf2
|
||||
size 138647
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0b01d2cef8bcd3400a276b8e4a4757ab2dd2c431ea00892e7a609319988022db
|
||||
size 137928
|
||||
oid sha256:1c83c28c9e8ee2196c5874fe958f1003aae29156afac721478578dc372706d48
|
||||
size 140846
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1c17e1eb73421a3c3ef948f0c1007673a67130ff126d4eb54833e56b82e3f96a
|
||||
size 109992
|
||||
oid sha256:066a49f7d5f1e8fd46a3f35c403bd1b8257e5b30ad6c3af15ea666a783e1f399
|
||||
size 113268
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:010e94e5b3d969f7141afb1951fcb6c87dd9ea6a48dc944dfeb8bac3a1e66e74
|
||||
size 142139
|
||||
oid sha256:337b7463033f2b86866f880461402139586b960333b6f0e628b57eb3b32a0454
|
||||
size 145428
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1f8bb38f2fef834933223d9f2fde4e9739a93e025dafbec63931e4f4e35e7a19
|
||||
size 73279
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c2b9a7343b1c186a071eed7a3c337101cc0efb569b612f1b5e0737a84e798355
|
||||
size 113574
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6398dc3faa59f76171d5b07d51189b28ea8c5b4842e65b2c6b2d2ef8c9a876fc
|
||||
size 130901
|
||||
oid sha256:9e7814adddbd5a2ff51be824e5a842edd4c7ba7c2fdfdef635a6758e09cbf661
|
||||
size 133394
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8cd5e793eaf7b9d7c49e7a941f80d3cda9006a91035e36fa74f973bd707605a7
|
||||
size 178087
|
||||
oid sha256:1ff99b080b22b3c09001fc3fc835e3b41c877f5e7f2cd6cfe26ef2f7c01a54c1
|
||||
size 176256
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:879e4c49e9a0e3ea321f30f0fe4e157abb0576ec97d116037b3fbd45425ee4b8
|
||||
size 179235
|
||||
oid sha256:9988534198d133d1f9e020e6af5628259bea9c6fdda64ac5b168230dc0a4621e
|
||||
size 176611
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9ce09743029cf0f0c0538c23dce32014338a186d3b647bbefba1c4eb43d5962b
|
||||
size 137992
|
||||
oid sha256:e91d758ef441d68019b14c2416d761f9f522321f8d1e9d826072eb3fcf07dee6
|
||||
size 142550
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:374b2e5744e3006e6cf0424bfbe05126465bd8ef3e1926ab1bd370797bbce441
|
||||
size 189541
|
||||
oid sha256:2a99249f04ea968b762cf14ff335f048eb7d91374c01761d8262c526f858431e
|
||||
size 194812
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:eddf4597e366941142a3df33e8341465b373eac43fb5f285a2a3a39f2d81d3df
|
||||
size 79393
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c6f01046e054115621ff5ef7189e5bf791555d9729af959c6e38a8a048f4350e
|
||||
size 136913
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9cccaa927653f0ad0c743af8b3638e9dc05329cee2c9be75b0c538b33ab248e8
|
||||
size 119658
|
||||
oid sha256:3288dfb3ed775665e37c214c4f2d2033e3174f22f41006f5fe0d1878a60866c1
|
||||
size 123312
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5ae457bdca037f41371586172d1ad7856d0e204257f55de0b8ac56e4e1cf0100
|
||||
size 149272
|
||||
oid sha256:47e87c18eb21430919e24ebc8307531e479e03fefffc001e45623739ea9ec62f
|
||||
size 154961
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6e50c3b2e4ea93ac0eb7ac8fe76ce705c2ad70acf9d45169deb27377d811b3b1
|
||||
size 153678
|
||||
oid sha256:be36151a47830413040624f7d0922bd389321e11387e72c7581f691b3ea37b94
|
||||
size 156406
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2012b95de7998c0321c7f8c7e14a5c336064eab574418f0110007c41098d1d75
|
||||
size 125205
|
||||
oid sha256:40e4ca832c774e501232f9d4a5dfaceae34d72d3a8f4a0647ff26afa73f5552e
|
||||
size 129593
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1e1582e788c142a48c58b95109f9e61ea4ca33039457614fe7abf329da331d72
|
||||
size 157243
|
||||
oid sha256:46290c09fa96860d002308e7fdc80dd9840c3ed12567489343ed568e1d11c55c
|
||||
size 160957
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9606120bc5a84596e5c8fd0fc5ed645327ad2dcb231520cb3359ee6fe3250a4a
|
||||
size 76919
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fd4d7f3b5f97247a3dabd79ad1349649b64f075c88cb7e44b79aa77e21930e1b
|
||||
size 126359
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:83076ceeb1fab6a333305fd9810c6a4475ac2779ba0154ef9f94570f6131a4ea
|
||||
size 165487
|
||||
oid sha256:380a2caf2250cba0f7f69763860958ef8d3107d6841b10e139221022f719f7e2
|
||||
size 165836
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce6b94d85f64b6687c8cfbe91b25811b3b28ce8ef2350ffc0bb18852be0930e9
|
||||
size 206487
|
||||
oid sha256:ede47654000517fbf67ef6085f4b9c9574b50a73723925690a868dfad1c4684a
|
||||
size 207805
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fe7064493f7f4838af12b13eb51295ae68163ceae6740bda6dbdbd36705ccc29
|
||||
size 194311
|
||||
oid sha256:0ef967694784f25e8e3614498d8ada07569910917432815523e34a9a4f79f301
|
||||
size 178881
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4502d0e8b1e37f8917226b425be5c6c50d9eaaf6c65d43cd80e6b391ad8e8157
|
||||
size 168510
|
||||
oid sha256:360f724b77a289e54e99ecb225e33cb30252a23bcae18f7f6de287f550c37b70
|
||||
size 171690
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7ff50bea189beb7dcfa9efb5d3cfcadbdf5abe5c5a3b7db924719de2ab48e545
|
||||
size 214384
|
||||
oid sha256:e75114908cf9e7049ceafeda01d5c69eb48a54bef628245a5eecd805e00e8026
|
||||
size 201023
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:93857a1d768489bd8854e67520d436c31a73cb861a78509a1255cc92d8ceef5b
|
||||
size 85920
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b2c163403ced353a3d7871275cf8619d7d9ab8da7c2819282043458f233011ee
|
||||
size 160495
|
||||
189
UnitTests/Sources/RoomPollsHistoryScreenViewModelTests.swift
Normal file
189
UnitTests/Sources/RoomPollsHistoryScreenViewModelTests.swift
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class RoomPollsHistoryScreenViewModelTests: XCTestCase {
|
||||
var viewModel: RoomPollsHistoryScreenViewModelProtocol!
|
||||
var interactionHandler: PollInteractionHandlerMock!
|
||||
var timelineController: MockRoomTimelineController!
|
||||
|
||||
var context: RoomPollsHistoryScreenViewModelType.Context {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
interactionHandler = PollInteractionHandlerMock()
|
||||
timelineController = MockRoomTimelineController()
|
||||
let roomProxyMockConfiguration = RoomProxyMockConfiguration(displayName: "Polls")
|
||||
roomProxyMockConfiguration.timeline.timelineStartReached = false
|
||||
viewModel = RoomPollsHistoryScreenViewModel(roomProxy: RoomProxyMock(with: roomProxyMockConfiguration),
|
||||
pollInteractionHandler: interactionHandler,
|
||||
roomTimelineController: timelineController,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
}
|
||||
|
||||
func testBackPaginate() async throws {
|
||||
timelineController.backPaginationResponses = [
|
||||
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
|
||||
PollRoomTimelineItem.mock(poll: .endedDisclosed)]
|
||||
]
|
||||
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false])
|
||||
|
||||
viewModel.context.send(viewAction: .loadMore)
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.viewState.pollTimelineItems.count, 3)
|
||||
XCTAssertFalse(viewModel.context.viewState.canBackPaginate)
|
||||
}
|
||||
|
||||
func testBackPaginateCanBackPaginate() async throws {
|
||||
timelineController.backPaginationResponses = [
|
||||
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
|
||||
PollRoomTimelineItem.mock(poll: .endedDisclosed)],
|
||||
[]
|
||||
]
|
||||
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false])
|
||||
|
||||
viewModel.context.send(viewAction: .loadMore)
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.viewState.pollTimelineItems.count, 3)
|
||||
XCTAssert(viewModel.context.viewState.canBackPaginate)
|
||||
}
|
||||
|
||||
func testBackPaginateTwice() async throws {
|
||||
timelineController.backPaginationResponses = [
|
||||
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false))],
|
||||
[PollRoomTimelineItem.mock(poll: .endedDisclosed)]
|
||||
]
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.isBackPaginating, transitionValues: [false, true, false])
|
||||
|
||||
viewModel.context.send(viewAction: .loadMore)
|
||||
viewModel.context.send(viewAction: .loadMore)
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.viewState.pollTimelineItems.count, 3)
|
||||
XCTAssert(viewModel.context.viewState.canBackPaginate)
|
||||
}
|
||||
|
||||
func testFilters() async throws {
|
||||
timelineController.backPaginationResponses = [
|
||||
[PollRoomTimelineItem.mock(poll: .emptyDisclosed, isEditable: true),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true)),
|
||||
PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: false)),
|
||||
PollRoomTimelineItem.mock(poll: .endedDisclosed)],
|
||||
[]
|
||||
]
|
||||
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
!value.pollTimelineItems.isEmpty
|
||||
}
|
||||
|
||||
viewModel.context.filter = .ongoing
|
||||
viewModel.context.send(viewAction: .loadMore)
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.viewState.pollTimelineItems.count, 3)
|
||||
|
||||
viewModel.context.send(viewAction: .filter(.past))
|
||||
XCTAssertEqual(viewModel.context.viewState.pollTimelineItems.count, 1)
|
||||
}
|
||||
|
||||
func testEndPoll() async throws {
|
||||
let deferred = deferFulfillment(interactionHandler.publisher) { _ in true }
|
||||
|
||||
interactionHandler.endPollPollStartIDReturnValue = .success(())
|
||||
viewModel.context.send(viewAction: .end(pollStartID: "somePollID"))
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(interactionHandler.endPollPollStartIDCalled)
|
||||
XCTAssertEqual(interactionHandler.endPollPollStartIDReceivedPollStartID, "somePollID")
|
||||
}
|
||||
|
||||
func testEndPollFailure() async throws {
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.bindings.alertInfo != nil
|
||||
}
|
||||
|
||||
interactionHandler.endPollPollStartIDReturnValue = .failure(TimelineProxyError.failedEndingPoll)
|
||||
viewModel.context.send(viewAction: .end(pollStartID: "somePollID"))
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(interactionHandler.endPollPollStartIDCalled)
|
||||
XCTAssertEqual(interactionHandler.endPollPollStartIDReceivedPollStartID, "somePollID")
|
||||
}
|
||||
|
||||
func testSendPollResponse() async throws {
|
||||
let deferred = deferFulfillment(interactionHandler.publisher) { _ in true }
|
||||
|
||||
interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .success(())
|
||||
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID"))
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(interactionHandler.sendPollResponsePollStartIDOptionIDCalled)
|
||||
XCTAssertEqual(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].pollStartID, "somePollID")
|
||||
XCTAssertEqual(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].optionID, "someOptionID")
|
||||
}
|
||||
|
||||
func testSendPollResponseFailure() async throws {
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.bindings.alertInfo != nil
|
||||
}
|
||||
|
||||
interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .failure(TimelineProxyError.failedSendingPollResponse)
|
||||
viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID"))
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(interactionHandler.sendPollResponsePollStartIDOptionIDCalled)
|
||||
XCTAssertEqual(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].pollStartID, "somePollID")
|
||||
XCTAssertEqual(interactionHandler.sendPollResponsePollStartIDOptionIDReceivedInvocations[0].optionID, "someOptionID")
|
||||
}
|
||||
|
||||
func testEditPoll() async throws {
|
||||
let expectedPoll: Poll = .emptyDisclosed
|
||||
let expectedPollStartID = "someEventID"
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .editPoll(let pollStartID, let poll):
|
||||
expectedPollStartID == pollStartID && expectedPoll == poll
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.context.send(viewAction: .edit(pollStartID: expectedPollStartID, poll: expectedPoll))
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b0c5b464a748ad0862049a76bb4f187c679034cc28898447c5058c4931789ac
|
||||
size 116006
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d3e3a08b89f04a344b1eeedd42100ea950063a5cd0bab3eb944fc12f9e19951a
|
||||
size 115364
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cda1d8f5b00bce67897298cfd4c8f13449c1a4925648845abea59f0b9ad95156
|
||||
size 108965
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72a22c525adf7ae1a33801dea692ce7774d285e8b1c4430167da71709e990c32
|
||||
size 109045
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72a22c525adf7ae1a33801dea692ce7774d285e8b1c4430167da71709e990c32
|
||||
size 109045
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f8d5def15c593ab91d412d432957ca8fbc73c552dc45d8940d32b6f4d512b02
|
||||
size 103783
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e0f795c81b1de86d8fd339a4a41da77e1ba5708ff7f1d353a84cd72872b85ae
|
||||
size 170145
|
||||
oid sha256:3385ad85711489e26d8a7bc8d4951595fbb860f78b894bbdede811b33da4e875
|
||||
size 169187
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:71a9439c43fa9d524757b86286c97364fb36862f7a5baaf85195d3f6c838e9f4
|
||||
size 174322
|
||||
oid sha256:df5bf3bdc2a327abc5acff85eacf22da2daea56cd87edb20d166bf63ea6c65c5
|
||||
size 173146
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:485b9d96a003d14cfcc81a607e1cf22277b7a6daabe14cfaf3ab817dd9ff2040
|
||||
size 105327
|
||||
oid sha256:1d62c100d777596fbec7d9f7d6ad03983f7963bdab029ed231667bdca5db767e
|
||||
size 112041
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:283f397347f3307f448ca2a271ef6e7ec8a71e697c4929e5b8c3d3464e02cf40
|
||||
size 93617
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf0718060176eba8372ec98d6d2ceddd12a0f25e25bebda7fec9fa0ea904b482
|
||||
size 201120
|
||||
1
changelog.d/2230.feature
Normal file
1
changelog.d/2230.feature
Normal file
@@ -0,0 +1 @@
|
||||
The poll history can be viewed in the room details.
|
||||
Reference in New Issue
Block a user