diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 983823568..aeaf573a6 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXAggregateTarget section */ @@ -399,6 +399,7 @@ 5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; }; 5D27B6537591471A42C89027 /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450E04B2A976CC4C8CC1807C /* EmoteRoomTimelineItem.swift */; }; 5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; }; + 5D52925FEB1B780C65B0529F /* PinnedEventsTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4F6D7000EDCD187E0989E7 /* PinnedEventsTimelineScreen.swift */; }; 5D53AE9342A4C06B704247ED /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; }; 5D56CE09743C6B90C21B04C2 /* RoomMembersListScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */; }; 5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; }; @@ -407,6 +408,7 @@ 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; }; 5EE1D4E316D66943E97FDCF2 /* BloomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BEB970F500BFB248443FA1 /* BloomView.swift */; }; 5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; }; + 5F0B5797D1BFF2A51084B4C3 /* PinnedEventsTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D7CD5CA270BFC3EBB450CA /* PinnedEventsTimelineScreenViewModel.swift */; }; 5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; }; 5FCD8AFA364206EE32B909A3 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B050A6B233D95807A09289E7 /* Settings.bundle */; }; 601AB75BD52B0B4276CEB84A /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */; }; @@ -533,6 +535,7 @@ 7A8B264506D3DDABC01B4EEB /* AppMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53AC78E49A297AC1D72A7CF /* AppMediator.swift */; }; 7AE82514D96C725F8BDD0ED4 /* HighlightedTimelineItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D162B2280A15ACAF35360554 /* HighlightedTimelineItemModifier.swift */; }; 7B1605C6FFD4D195F264A684 /* RoomPollsHistoryScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */; }; + 7B3A59786DB2F741A1743ED0 /* PinnedEventsTimelineScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510E89B989477E5EE8E503C0 /* PinnedEventsTimelineScreenViewModelProtocol.swift */; }; 7B5DAB915357BE596529BF25 /* MapTilerStaticMapProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20872C3887F835958CE2F1D0 /* MapTilerStaticMapProtocol.swift */; }; 7B66DA4E7E5FE4D1A0FCEAA4 /* JoinRoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAB5662310AE73D93815134 /* JoinRoomScreenViewModelProtocol.swift */; }; 7BB31E67648CF32D2AB5E502 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */; }; @@ -967,6 +970,7 @@ E0C167D41A48EDB30B447DE3 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A5C3F7C9C1DA10CAEC6A98 /* VoiceMessageRecordingComposer.swift */; }; E0FB26262689F04D66A949D7 /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E227F34BE43B08E098796E /* TestablePreview.swift */; }; E14E469CD97550D0FC58F3CA /* CancellableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52983FAFB4E0998C00EE8A /* CancellableTask.swift */; }; + E184FFAD32342D3D6E2F89AA /* PinnedEventsTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D53754227CEBD06358956D7 /* PinnedEventsTimelineScreenCoordinator.swift */; }; E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; E1F446C6B78A3A0FEA15079C /* UnsupportedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */; }; E21FE4C5B614F311C0955859 /* UserProfileProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */; }; @@ -1102,6 +1106,7 @@ FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; }; FDD5B4B616D9FF4DE3E9A418 /* QRCodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DB574F954CC2B40F7BE892 /* QRCodeScannerView.swift */; }; FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; }; + FEC03105D1BDE0F49BD7F243 /* PinnedEventsTimelineScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B6572E6EF5D5F4B0C338A40 /* PinnedEventsTimelineScreenModels.swift */; }; FEFD5290B31FCBA6999912C8 /* RoomChangePermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2721D7B051F0159AA919DA05 /* RoomChangePermissionsScreenViewModelProtocol.swift */; }; FF34BF2AF731340AF9414A18 /* SwipeRightAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4552D3466B1453F287223ADA /* SwipeRightAction.swift */; }; FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */; }; @@ -1176,13 +1181,13 @@ 033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; 0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = ""; }; - 0392E3FDE372C9B56FEEED8B /* test_voice_message.m4a */ = {isa = PBXFileReference; path = test_voice_message.m4a; sourceTree = ""; }; + 0392E3FDE372C9B56FEEED8B /* test_voice_message.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_voice_message.m4a; sourceTree = ""; }; 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = ""; }; 044E501B8331B339874D1B96 /* CompoundIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundIcon.swift; sourceTree = ""; }; 045253F9967A535EE5B16691 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; 046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryTests.swift; sourceTree = ""; }; - 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; 0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxyProtocol.swift; sourceTree = ""; }; @@ -1242,7 +1247,7 @@ 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -1333,7 +1338,7 @@ 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = ""; }; 25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenCoordinator.swift; sourceTree = ""; }; 260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = ""; }; - 267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; path = PreviewTests.xctestplan; sourceTree = ""; }; + 267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PreviewTests.xctestplan; sourceTree = ""; }; 26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = ""; }; 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = ""; }; 2721D7B051F0159AA919DA05 /* RoomChangePermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1398,7 +1403,7 @@ 3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = ""; }; 35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 35FA991289149D31F4286747 /* UserPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreference.swift; sourceTree = ""; }; - 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyMock.swift; sourceTree = ""; }; 376D941BF8BB294389C0DE24 /* MapTilerURLBuildersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerURLBuildersTests.swift; sourceTree = ""; }; 37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = ""; }; @@ -1513,6 +1518,7 @@ 50D685B4DB38BB5BD87C956A /* AuthenticationStartScreenBackgroundImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenBackgroundImage.swift; sourceTree = ""; }; 50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = ""; }; 50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = ""; }; + 510E89B989477E5EE8E503C0 /* PinnedEventsTimelineScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenViewModelProtocol.swift; sourceTree = ""; }; 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; 514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkCoordinator.swift; sourceTree = ""; }; 51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = ""; }; @@ -1559,6 +1565,7 @@ 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = ""; }; 5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogProtocol.swift; sourceTree = ""; }; 5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = ""; }; + 5D53754227CEBD06358956D7 /* PinnedEventsTimelineScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenCoordinator.swift; sourceTree = ""; }; 5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledFrameModifier.swift; sourceTree = ""; }; 5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetection.swift; sourceTree = ""; }; 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProperties.swift; sourceTree = ""; }; @@ -1725,6 +1732,7 @@ 869A8A4632E511351BFE2EC4 /* JoinRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreen.swift; sourceTree = ""; }; 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModel.swift; sourceTree = ""; }; 86C8CE2630F54D5FE1591786 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; }; + 86D7CD5CA270BFC3EBB450CA /* PinnedEventsTimelineScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenViewModel.swift; sourceTree = ""; }; 88410BD213FDF9B28E8B671F /* UserDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreen.swift; sourceTree = ""; }; 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreen.swift; sourceTree = ""; }; 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerProtocol.swift; sourceTree = ""; }; @@ -1749,7 +1757,7 @@ 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorMock.swift; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreen.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectoryCell.swift; sourceTree = ""; }; @@ -1799,6 +1807,7 @@ 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = ""; }; 9AA3AF94A06D319BB37E52DA /* TimelineItemFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemFactoryTests.swift; sourceTree = ""; }; 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreen.swift; sourceTree = ""; }; + 9B6572E6EF5D5F4B0C338A40 /* PinnedEventsTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenModels.swift; sourceTree = ""; }; 9B663BE498BB39EADC24025D /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = ""; }; 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = ""; }; 9B7D8D3638864B7482E148CC /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1913,7 +1922,7 @@ B53AC78E49A297AC1D72A7CF /* AppMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediator.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = ""; }; @@ -2005,6 +2014,7 @@ C9F893F4A111CB7BA5C96949 /* AppLockSetupBiometricsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModel.swift; sourceTree = ""; }; CA28F29C9F93E93CC3C2C715 /* NavigationRootCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRootCoordinator.swift; sourceTree = ""; }; CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModelTests.swift; sourceTree = ""; }; + CA4F6D7000EDCD187E0989E7 /* PinnedEventsTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreen.swift; sourceTree = ""; }; CA89A2DD51B6BBE1DA55E263 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; CA8F098AE48D958B4257EB24 /* CallInviteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInviteRoomTimelineView.swift; sourceTree = ""; }; CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenCoordinator.swift; sourceTree = ""; }; @@ -2026,7 +2036,7 @@ CE47A97726F0675DEE387BF9 /* TypingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; @@ -2154,7 +2164,7 @@ ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; @@ -2177,7 +2187,7 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreen.swift; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; @@ -2464,6 +2474,14 @@ path = BugReport; sourceTree = ""; }; + 101EB4449E23F8A2892E4A63 /* View */ = { + isa = PBXGroup; + children = ( + CA4F6D7000EDCD187E0989E7 /* PinnedEventsTimelineScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 10578D9852BA78D309A1CBDF /* ViewModel */ = { isa = PBXGroup; children = ( @@ -2990,6 +3008,18 @@ path = QRCodeLoginScreen; sourceTree = ""; }; + 3E535010B850B53DDD3CFF2A /* PinnedEventsTimelineScreen */ = { + isa = PBXGroup; + children = ( + 5D53754227CEBD06358956D7 /* PinnedEventsTimelineScreenCoordinator.swift */, + 9B6572E6EF5D5F4B0C338A40 /* PinnedEventsTimelineScreenModels.swift */, + 86D7CD5CA270BFC3EBB450CA /* PinnedEventsTimelineScreenViewModel.swift */, + 510E89B989477E5EE8E503C0 /* PinnedEventsTimelineScreenViewModelProtocol.swift */, + 101EB4449E23F8A2892E4A63 /* View */, + ); + path = PinnedEventsTimelineScreen; + sourceTree = ""; + }; 3E69187DDC5E781EB96A7BED /* ItemMenu */ = { isa = PBXGroup; children = ( @@ -5077,6 +5107,7 @@ 3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */, 8F074E22FD93E64211971845 /* Onboarding */, A448A3A8F764174C60CD0CA1 /* Other */, + 3E535010B850B53DDD3CFF2A /* PinnedEventsTimelineScreen */, 3D733E8352DD4C461CFD8B8A /* QRCodeLoginScreen */, 5970F275D6014548DCED6106 /* ReportContentScreen */, DAB7DC51866A6D1B51BDC3A2 /* RoomChangePermissionsScreen */, @@ -6425,6 +6456,11 @@ 7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */, 8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */, 7E2BB42805C59DB57E95610F /* PillView.swift in Sources */, + 5D52925FEB1B780C65B0529F /* PinnedEventsTimelineScreen.swift in Sources */, + E184FFAD32342D3D6E2F89AA /* PinnedEventsTimelineScreenCoordinator.swift in Sources */, + FEC03105D1BDE0F49BD7F243 /* PinnedEventsTimelineScreenModels.swift in Sources */, + 5F0B5797D1BFF2A51084B4C3 /* PinnedEventsTimelineScreenViewModel.swift in Sources */, + 7B3A59786DB2F741A1743ED0 /* PinnedEventsTimelineScreenViewModelProtocol.swift in Sources */, 053B8BD2496207838878C6C9 /* PinnedItemsBannerView.swift in Sources */, D26093BB80B69092B0E9AC7C /* PinnedItemsIndicatorView.swift in Sources */, 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */, @@ -7024,9 +7060,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; @@ -7075,9 +7109,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -7103,9 +7135,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -7350,9 +7380,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 5157ad00b..e73aff3a6 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -315,11 +315,15 @@ "screen_advanced_settings_element_call_base_url" = "Custom Element Call base URL"; "screen_advanced_settings_element_call_base_url_description" = "Set a custom base URL for Element Call."; "screen_advanced_settings_element_call_base_url_validation_error" = "Invalid URL, please make sure you include the protocol (http/https) and the correct address."; +"screen_pinned_timeline_empty_state_description" = "Press on a message and choose “%1$@” to include here."; +"screen_pinned_timeline_empty_state_headline" = "Pin important messages so that they can be easily discovered"; +"screen_pinned_timeline_screen_title_empty" = "Pinned messages"; "screen_room_mentions_at_room_subtitle" = "Notify the whole room"; "screen_room_pinned_banner_indicator" = "%1$@ of %2$@"; "screen_room_pinned_banner_indicator_description" = "%1$@ Pinned messages"; "screen_room_pinned_banner_loading_description" = "Loading message…"; "screen_room_pinned_banner_view_all_button_title" = "View All"; +"screen_room_details_pinned_events_row_title" = "Pinned messages"; "screen_account_provider_change" = "Change account provider"; "screen_account_provider_form_hint" = "Homeserver address"; "screen_account_provider_form_notice" = "Enter a search term or a domain address."; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict b/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict index 3ddd00f7e..60f8827b5 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict @@ -194,6 +194,22 @@ Wrong PIN. You have %1$d more chances + screen_pinned_timeline_screen_title + + NSStringLocalizedFormatKey + %#@COUNT@ + COUNT + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d Pinned message + other + %1$d Pinned messages + + screen_room_member_list_header_title NSStringLocalizedFormatKey diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index f2684d428..030b57219 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -72,7 +72,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg appHooks.configure() windowManager = WindowManager(appDelegate: appDelegate) - appMediator = AppMediator(windowManager: windowManager) + let networkMonitor = NetworkMonitor() + appMediator = AppMediator(windowManager: windowManager, networkMonitor: networkMonitor) let appSettings = appHooks.appSettingsHook.configure(AppSettings()) @@ -102,7 +103,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) - userSessionStore = UserSessionStore(keychainController: keychainController, appSettings: appSettings, appHooks: appHooks) + userSessionStore = UserSessionStore(keychainController: keychainController, appSettings: appSettings, appHooks: appHooks, networkMonitor: networkMonitor) let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings) let appLockNavigationCoordinator = NavigationRootCoordinator() @@ -342,7 +343,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private static func setupServiceLocator(appSettings: AppSettings, appHooks: AppHooks) { ServiceLocator.shared.register(userIndicatorController: UserIndicatorController()) ServiceLocator.shared.register(appSettings: appSettings) - ServiceLocator.shared.register(networkMonitor: NetworkMonitor()) ServiceLocator.shared.register(bugReportService: BugReportService(withBaseURL: appSettings.bugReportServiceBaseURL, applicationId: appSettings.bugReportApplicationId, sdkGitSHA: sdkGitSha(), @@ -682,7 +682,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private func observeNetworkState() { let reachabilityNotificationIdentifier = "io.element.elementx.reachability.notification" - ServiceLocator.shared.networkMonitor + appMediator.networkMonitor .reachabilityPublisher .sink { reachability in MXLog.info("Reachability changed to \(reachability)") @@ -851,12 +851,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg .loadingStatePublisher .removeDuplicates() .receive(on: DispatchQueue.main) - .sink { state in + .sink { [weak self] state in let toastIdentifier = "StaleDataIndicator" switch state { case .loading: - if ServiceLocator.shared.networkMonitor.reachabilityPublisher.value == .reachable { + if self?.appMediator.networkMonitor.reachabilityPublisher.value == .reachable { ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: toastIdentifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) } case .notLoading: diff --git a/ElementX/Sources/Application/AppMediator.swift b/ElementX/Sources/Application/AppMediator.swift index c836d12c4..8192976e5 100644 --- a/ElementX/Sources/Application/AppMediator.swift +++ b/ElementX/Sources/Application/AppMediator.swift @@ -19,9 +19,11 @@ import UIKit class AppMediator: AppMediatorProtocol { let windowManager: WindowManagerProtocol + let networkMonitor: NetworkMonitorProtocol - init(windowManager: WindowManagerProtocol) { + init(windowManager: WindowManagerProtocol, networkMonitor: NetworkMonitorProtocol) { self.windowManager = windowManager + self.networkMonitor = networkMonitor } // UIApplication.State won't update if we store this e.g. in the constructor diff --git a/ElementX/Sources/Application/AppMediatorProtocol.swift b/ElementX/Sources/Application/AppMediatorProtocol.swift index 58fe34041..fd3364f4f 100644 --- a/ElementX/Sources/Application/AppMediatorProtocol.swift +++ b/ElementX/Sources/Application/AppMediatorProtocol.swift @@ -21,6 +21,7 @@ import UIKit @MainActor protocol AppMediatorProtocol { var windowManager: WindowManagerProtocol { get } + var networkMonitor: NetworkMonitorProtocol { get } var appState: UIApplication.State { get } diff --git a/ElementX/Sources/Application/ServiceLocator.swift b/ElementX/Sources/Application/ServiceLocator.swift index 3ebe720f9..7b2cb355a 100644 --- a/ElementX/Sources/Application/ServiceLocator.swift +++ b/ElementX/Sources/Application/ServiceLocator.swift @@ -33,12 +33,6 @@ class ServiceLocator { settings = appSettings } - private(set) var networkMonitor: NetworkMonitorProtocol! - - func register(networkMonitor: NetworkMonitorProtocol) { - self.networkMonitor = networkMonitor - } - private(set) var analytics: AnalyticsService! func register(analytics: AnalyticsService) { diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 81619663c..bbd3d67eb 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -325,7 +325,19 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { return .pollForm case (.pollForm, .dismissPollForm): return .room - + + case (.room, .presentPinnedEventsTimeline): + return .pinnedEventsTimeline(previousState: .room) + case (.roomDetails(let isRoot), .presentPinnedEventsTimeline): + return .pinnedEventsTimeline(previousState: .details(isRoot: isRoot)) + case (.pinnedEventsTimeline(let previousState), .dismissPinnedEventsTimeline): + switch previousState { + case .room: + return .room + case .details(let isRoot): + return .roomDetails(isRoot: isRoot) + } + case (.roomDetails, .presentPollsHistory): return .pollsHistory case (.pollsHistory, .dismissPollsHistory): @@ -457,11 +469,21 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { presentPollForm(mode: mode) case (.pollForm, .dismissPollForm, .room): break + + case (.room, .presentPinnedEventsTimeline, .pinnedEventsTimeline): + presentPinnedEventsTimeline() + case (.pinnedEventsTimeline, .dismissPinnedEventsTimeline, .room): + break case (.roomDetails, .presentPollsHistory, .pollsHistory): presentPollsHistory() case (.pollsHistory, .dismissPollsHistory, .roomDetails): break + + case (.roomDetails, .presentPinnedEventsTimeline, .pinnedEventsTimeline): + presentPinnedEventsTimeline() + case (.pinnedEventsTimeline, .dismissPinnedEventsTimeline, .roomDetails): + break case (.pollsHistory, .presentPollForm(let mode), .pollsHistoryForm): presentPollForm(mode: mode) @@ -595,6 +617,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.presentMessageForwarding(forwardingItem: forwardingItem)) case .presentCallScreen: actionsSubject.send(.presentCallScreen(roomProxy: roomProxy)) + case .presentPinnedEventsTimeline: + stateMachine.tryEvent(.presentPinnedEventsTimeline) } } .store(in: &cancellables) @@ -693,7 +717,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { analyticsService: analytics, userIndicatorController: userIndicatorController, notificationSettings: userSession.clientProxy.notificationSettings, - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: appMediator) let coordinator = RoomDetailsScreenCoordinator(parameters: params) coordinator.actions.sink { [weak self] action in guard let self else { return } @@ -715,6 +740,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.presentRolesAndPermissionsScreen) case .presentCall: actionsSubject.send(.presentCallScreen(roomProxy: roomProxy)) + case .presentPinnedEventsTimeline: + stateMachine.tryEvent(.presentPinnedEventsTimeline) } } .store(in: &cancellables) @@ -937,6 +964,28 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { self?.stateMachine.tryEvent(.dismissMapNavigator) } } + + private func presentPinnedEventsTimeline() { + let stackCoordinator = NavigationStackCoordinator() + let coordinator = PinnedEventsTimelineScreenCoordinator(parameters: .init()) + + coordinator.actions + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .dismiss: + navigationStackCoordinator.setSheetCoordinator(nil) + } + } + .store(in: &cancellables) + + stackCoordinator.setRootCoordinator(coordinator) + + navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in + self?.stateMachine.tryEvent(.dismissPinnedEventsTimeline) + } + } private func presentPollForm(mode: PollFormMode) { let stackCoordinator = NavigationStackCoordinator() @@ -1356,6 +1405,7 @@ private extension RoomFlowCoordinator { case pollsHistory case pollsHistoryForm case rolesAndPermissions + case pinnedEventsTimeline(previousState: PinnedEventsTimelineSource) /// A child flow is in progress. case presentingChild(childRoomID: String, previousState: State) @@ -1425,6 +1475,9 @@ private extension RoomFlowCoordinator { case presentRolesAndPermissionsScreen case dismissRolesAndPermissionsScreen + case presentPinnedEventsTimeline + case dismissPinnedEventsTimeline + // Child room flow events case startChildFlow(roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) case dismissChildFlow @@ -1447,3 +1500,8 @@ private extension Result { } } } + +private enum PinnedEventsTimelineSource: Hashable { + case room + case details(isRoot: Bool) +} diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index e1afba8bc..e052050fe 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -602,7 +602,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private func presentSecureBackupLogoutConfirmationScreen() { let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController, - networkMonitor: ServiceLocator.shared.networkMonitor)) + appMediator: appMediator)) coordinator.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index c2e27aba2..a88f3f404 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -1237,6 +1237,18 @@ internal enum L10n { } /// Be in your element internal static var screenOnboardingWelcomeTitle: String { return L10n.tr("Localizable", "screen_onboarding_welcome_title") } + /// Press on a message and choose “%1$@” to include here. + internal static func screenPinnedTimelineEmptyStateDescription(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_pinned_timeline_empty_state_description", String(describing: p1)) + } + /// Pin important messages so that they can be easily discovered + internal static var screenPinnedTimelineEmptyStateHeadline: String { return L10n.tr("Localizable", "screen_pinned_timeline_empty_state_headline") } + /// Plural format key: "%#@COUNT@" + internal static func screenPinnedTimelineScreenTitle(_ p1: Int) -> String { + return L10n.tr("Localizable", "screen_pinned_timeline_screen_title", p1) + } + /// Pinned messages + internal static var screenPinnedTimelineScreenTitleEmpty: String { return L10n.tr("Localizable", "screen_pinned_timeline_screen_title_empty") } /// Can't find any ongoing polls. internal static var screenPollsHistoryEmptyOngoing: String { return L10n.tr("Localizable", "screen_polls_history_empty_ongoing") } /// Can't find any past polls. @@ -1535,6 +1547,8 @@ internal enum L10n { internal static var screenRoomDetailsNotificationModeDefault: String { return L10n.tr("Localizable", "screen_room_details_notification_mode_default") } /// Notifications internal static var screenRoomDetailsNotificationTitle: String { return L10n.tr("Localizable", "screen_room_details_notification_title") } + /// Pinned messages + internal static var screenRoomDetailsPinnedEventsRowTitle: String { return L10n.tr("Localizable", "screen_room_details_pinned_events_row_title") } /// Roles and permissions internal static var screenRoomDetailsRolesAndPermissions: String { return L10n.tr("Localizable", "screen_room_details_roles_and_permissions") } /// Room name diff --git a/ElementX/Sources/Mocks/AppMediatorMock.swift b/ElementX/Sources/Mocks/AppMediatorMock.swift index 6f940660c..79f4db7d0 100644 --- a/ElementX/Sources/Mocks/AppMediatorMock.swift +++ b/ElementX/Sources/Mocks/AppMediatorMock.swift @@ -23,6 +23,7 @@ extension AppMediatorMock { mock.underlyingAppState = .active mock.requestAuthorizationIfNeededUnderlyingReturnValue = true mock.underlyingWindowManager = WindowManagerMock() + mock.underlyingNetworkMonitor = NetworkMonitorMock.default return mock } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 21d701720..492de1ea3 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -808,6 +808,11 @@ class AppMediatorMock: AppMediatorProtocol { set(value) { underlyingWindowManager = value } } var underlyingWindowManager: WindowManagerProtocol! + var networkMonitor: NetworkMonitorProtocol { + get { return underlyingNetworkMonitor } + set(value) { underlyingNetworkMonitor = value } + } + var underlyingNetworkMonitor: NetworkMonitorProtocol! var appState: UIApplication.State { get { return underlyingAppState } set(value) { underlyingAppState = value } diff --git a/ElementX/Sources/Mocks/RoomProxyMock.swift b/ElementX/Sources/Mocks/RoomProxyMock.swift index b82e7daa2..5a3d5ca7d 100644 --- a/ElementX/Sources/Mocks/RoomProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomProxyMock.swift @@ -40,6 +40,7 @@ struct RoomProxyMockConfiguration { var canUserInvite = true var canUserTriggerRoomNotification = false var canUserJoinCall = true + var canUserPin = true var shouldUseAutoUpdatingTimeline = false } @@ -135,6 +136,7 @@ extension RoomProxyMock { } canUserTriggerRoomNotificationUserIDReturnValue = .success(configuration.canUserTriggerRoomNotification) canUserJoinCallUserIDReturnValue = .success(configuration.canUserJoinCall) + canUserPinOrUnpinUserIDReturnValue = .success(configuration.canUserPin) kickUserReturnValue = .success(()) banUserReturnValue = .success(()) diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift index e87962686..cfd5804e6 100644 --- a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a encryptionResetPassword remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift index 3ff982983..111756e53 100644 --- a/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift +++ b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a logViewer remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift index 4ef07ebd0..6911fb067 100644 --- a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a identityConfirmation remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift index 4d33fcc77..860bb757b 100644 --- a/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a identityConfirmed remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift new file mode 100644 index 000000000..fd886d0e9 --- /dev/null +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift @@ -0,0 +1,61 @@ +// +// 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 PinnedEventsTimelineScreenCoordinatorParameters { } + +enum PinnedEventsTimelineScreenCoordinatorAction { + case dismiss + + // Consider adding CustomStringConvertible conformance if the actions contain PII +} + +final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol { + private let parameters: PinnedEventsTimelineScreenCoordinatorParameters + private let viewModel: PinnedEventsTimelineScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: PinnedEventsTimelineScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = PinnedEventsTimelineScreenViewModel() + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .dismiss: + self.actionsSubject.send(.dismiss) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(PinnedEventsTimelineScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenModels.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenModels.swift new file mode 100644 index 000000000..40b07d635 --- /dev/null +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenModels.swift @@ -0,0 +1,32 @@ +// +// 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 PinnedEventsTimelineScreenViewModelAction { + case dismiss +} + +struct PinnedEventsTimelineScreenViewState: BindableState { + var title: String { + // TODO: Implement the non empty case + L10n.screenPinnedTimelineScreenTitleEmpty + } +} + +enum PinnedEventsTimelineScreenViewAction { + case close +} diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift new file mode 100644 index 000000000..06580c7ab --- /dev/null +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift @@ -0,0 +1,42 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +typealias PinnedEventsTimelineScreenViewModelType = StateStoreViewModel + +class PinnedEventsTimelineScreenViewModel: PinnedEventsTimelineScreenViewModelType, PinnedEventsTimelineScreenViewModelProtocol { + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init() { + super.init(initialViewState: PinnedEventsTimelineScreenViewState()) + } + + // MARK: - Public + + override func process(viewAction: PinnedEventsTimelineScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .close: + actionsSubject.send(.dismiss) + } + } +} diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModelProtocol.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModelProtocol.swift new file mode 100644 index 000000000..5743f7ea5 --- /dev/null +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModelProtocol.swift @@ -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 PinnedEventsTimelineScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: PinnedEventsTimelineScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift new file mode 100644 index 000000000..912efee14 --- /dev/null +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift @@ -0,0 +1,68 @@ +// +// 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 PinnedEventsTimelineScreen: View { + @ObservedObject var context: PinnedEventsTimelineScreenViewModel.Context + + var body: some View { + content + .navigationTitle(context.viewState.title) + .navigationBarTitleDisplayMode(.inline) + .toolbar { toolbar } + .background(.compound.bgCanvasDefault) + } + + private var content: some View { + // TODO: Implement switching between empty state and timeline + VStack(spacing: 16) { + HeroImage(icon: \.pin, style: .normal) + Text(L10n.screenPinnedTimelineEmptyStateHeadline) + .font(.compound.headingSMSemibold) + .foregroundStyle(.compound.textPrimary) + .multilineTextAlignment(.center) + Text(L10n.screenPinnedTimelineEmptyStateDescription(L10n.actionPin)) + .font(.compound.bodyMD) + .foregroundStyle(.compound.textSecondary) + .multilineTextAlignment(.center) + Spacer() + } + .padding(.top, 48) + .padding(.horizontal, 16) + } + + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + ToolbarItem(placement: .confirmationAction) { + Button(L10n.actionClose) { + context.send(viewAction: .close) + } + } + } +} + +// MARK: - Previews + +struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = PinnedEventsTimelineScreenViewModel() + static var previews: some View { + NavigationStack { + PinnedEventsTimelineScreen(context: viewModel.context) + } + } +} diff --git a/ElementX/Sources/Screens/QRCodeLoginScreen/QRCodeLoginScreenCoordinator.swift b/ElementX/Sources/Screens/QRCodeLoginScreen/QRCodeLoginScreenCoordinator.swift index 60f591f6f..0abcedec9 100644 --- a/ElementX/Sources/Screens/QRCodeLoginScreen/QRCodeLoginScreenCoordinator.swift +++ b/ElementX/Sources/Screens/QRCodeLoginScreen/QRCodeLoginScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a qRCodeLogin remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift index 29a1ef449..2ceb15530 100644 --- a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a roomChangePermissions remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenCoordinator.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenCoordinator.swift index 8c6383e70..854fd6639 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a roomChangeRoles remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift index 9fc7c5530..5a430f3e6 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift @@ -25,6 +25,7 @@ struct RoomDetailsScreenCoordinatorParameters { let userIndicatorController: UserIndicatorControllerProtocol let notificationSettings: NotificationSettingsProxyProtocol let attributedStringBuilder: AttributedStringBuilderProtocol + let appMediator: AppMediatorProtocol } enum RoomDetailsScreenCoordinatorAction { @@ -36,6 +37,7 @@ enum RoomDetailsScreenCoordinatorAction { case presentPollsHistory case presentRolesAndPermissionsScreen case presentCall + case presentPinnedEventsTimeline } final class RoomDetailsScreenCoordinator: CoordinatorProtocol { @@ -55,7 +57,9 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { analyticsService: parameters.analyticsService, userIndicatorController: parameters.userIndicatorController, notificationSettingsProxy: parameters.notificationSettings, - attributedStringBuilder: parameters.attributedStringBuilder) + attributedStringBuilder: parameters.attributedStringBuilder, + appMediator: parameters.appMediator, + appSettings: ServiceLocator.shared.settings) } // MARK: - Public @@ -82,6 +86,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.presentRolesAndPermissionsScreen) case .startCall: actionsSubject.send(.presentCall) + case .displayPinnedEventsTimeline: + actionsSubject.send(.presentPinnedEventsTimeline) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift index 0d3394470..428015525 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift @@ -30,6 +30,7 @@ enum RoomDetailsScreenViewModelAction { case requestPollsHistoryPresentation case requestRolesAndPermissionsPresentation case startCall + case displayPinnedEventsTimeline } // MARK: View @@ -52,6 +53,8 @@ struct RoomDetailsScreenViewState: BindableState { var canEditRolesOrPermissions = false var notificationSettingsState: RoomDetailsNotificationSettingsState = .loading var canJoinCall = false + var isPinningEnabled = false + var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading var canEdit: Bool { !isDirect && (canEditRoomName || canEditRoomTopic || canEditRoomAvatar) @@ -194,6 +197,7 @@ enum RoomDetailsScreenViewAction { case toggleFavourite(isFavourite: Bool) case processTapRolesAndPermissions case processTapCall + case processTapPinnedEvents } enum RoomDetailsScreenViewShortcut { @@ -261,3 +265,26 @@ enum RoomDetailsScreenErrorType: Hashable { /// Leaving room has failed.. case unknown } + +enum RoomDetailsScreenPinnedEventsActionState { + case loading + case loaded(numberOfItems: Int) + + var count: String { + switch self { + case .loading: + return "" + case .loaded(let numberOfItems): + return "\(numberOfItems)" + } + } + + var isLoading: Bool { + switch self { + case .loading: + return true + default: + return false + } + } +} diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index 854a2f51f..dc40f316a 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -27,8 +27,26 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr private let userIndicatorController: UserIndicatorControllerProtocol private let notificationSettingsProxy: NotificationSettingsProxyProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol + private let appSettings: AppSettings private var dmRecipient: RoomMemberProxyProtocol? + private var pinnedEventsTimelineProvider: RoomTimelineProviderProtocol? { + didSet { + guard let pinnedEventsTimelineProvider else { + return + } + + state.pinnedEventsActionState = .loaded(numberOfItems: pinnedEventsTimelineProvider.itemProxies.filter(\.isEvent).count) + + pinnedEventsTimelineProvider.updatePublisher + // When pinning or unpinning an item, the timeline might return empty for a short while, so we need to debounce it to prevent weird UI behaviours like the banner disappearing + .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main) + .sink { [weak self] updatedItems, _ in + self?.state.pinnedEventsActionState = .loaded(numberOfItems: updatedItems.filter(\.isEvent).count) + } + .store(in: &cancellables) + } + } private var actionsSubject: PassthroughSubject = .init() @@ -42,7 +60,9 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr analyticsService: AnalyticsService, userIndicatorController: UserIndicatorControllerProtocol, notificationSettingsProxy: NotificationSettingsProxyProtocol, - attributedStringBuilder: AttributedStringBuilderProtocol) { + attributedStringBuilder: AttributedStringBuilderProtocol, + appMediator: AppMediatorProtocol, + appSettings: AppSettings) { self.roomProxy = roomProxy self.clientProxy = clientProxy self.mediaProvider = mediaProvider @@ -50,6 +70,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr self.userIndicatorController = userIndicatorController self.notificationSettingsProxy = notificationSettingsProxy self.attributedStringBuilder = attributedStringBuilder + self.appSettings = appSettings let topic = attributedStringBuilder.fromPlain(roomProxy.topic) @@ -63,6 +84,19 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr bindings: .init()), imageProvider: mediaProvider) + appSettings.$pinningEnabled + .weakAssign(to: \.state.isPinningEnabled, on: self) + .store(in: &cancellables) + + appSettings.$pinningEnabled + .combineLatest(appMediator.networkMonitor.reachabilityPublisher) + .filter { $0.0 && $0.1 == .reachable } + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.setupPinnedEventsTimelineProviderIfNeeded() + } + .store(in: &cancellables) + Task { let userID = roomProxy.ownUserID if case let .success(permission) = await roomProxy.canUserJoinCall(userID: userID) { @@ -135,6 +169,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr actionsSubject.send(.requestRolesAndPermissionsPresentation) case .processTapCall: actionsSubject.send(.startCall) + case .processTapPinnedEvents: + actionsSubject.send(.displayPinnedEventsTimeline) } } @@ -337,6 +373,22 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr } } } + + private func setupPinnedEventsTimelineProviderIfNeeded() { + guard pinnedEventsTimelineProvider == nil else { + return + } + + Task { + guard let timelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else { + return + } + + if pinnedEventsTimelineProvider == nil { + pinnedEventsTimelineProvider = timelineProvider + } + } + } } private extension AttributedString { diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index 183bd185b..bae61abee 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -198,6 +198,16 @@ struct RoomDetailsScreen: View { context.send(viewAction: .toggleFavourite(isFavourite: newValue)) } + if context.viewState.isPinningEnabled { + ListRow(label: .default(title: L10n.screenRoomDetailsPinnedEventsRowTitle, + icon: \.pin), + details: context.viewState.pinnedEventsActionState.isLoading ? .isWaiting(true) : .title(context.viewState.pinnedEventsActionState.count), + kind: context.viewState.pinnedEventsActionState.isLoading ? .label : .navigationLink(action: { + context.send(viewAction: .processTapPinnedEvents) + })) + .disabled(context.viewState.pinnedEventsActionState.isLoading) + } + if context.viewState.canEditRolesOrPermissions, context.viewState.dmRecipient == nil { ListRow(label: .default(title: L10n.screenRoomDetailsRolesAndPermissions, icon: \.admin), @@ -320,6 +330,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { notificationSettingsProxyMockConfiguration.roomMode.isDefault = false let notificationSettingsProxy = NotificationSettingsProxyMock(with: notificationSettingsProxyMockConfiguration) let appSettings = AppSettings() + appSettings.pinningEnabled = true return RoomDetailsScreenViewModel(roomProxy: roomProxy, clientProxy: ClientProxyMock(.init()), @@ -327,7 +338,9 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxy, - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: appSettings) }() static let dmRoomViewModel = { @@ -345,6 +358,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { members: members)) let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) let appSettings = AppSettings() + appSettings.pinningEnabled = true return RoomDetailsScreenViewModel(roomProxy: roomProxy, clientProxy: ClientProxyMock(.init()), @@ -352,7 +366,9 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxy, - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: appSettings) }() static let simpleRoomViewModel = { @@ -369,6 +385,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { members: members)) let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) let appSettings = AppSettings() + appSettings.pinningEnabled = true return RoomDetailsScreenViewModel(roomProxy: roomProxy, clientProxy: ClientProxyMock(.init()), @@ -376,7 +393,9 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxy, - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: appSettings) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift index 07e5156a2..c805b6df4 100644 --- a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift @@ -14,8 +14,6 @@ // limitations under the License. // -// periphery:ignore:all - this is just a roomRolesAndPermissions remove this comment once generating the final file - import Combine import SwiftUI diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index c02b6c27e..62b1b59ef 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -45,6 +45,7 @@ enum RoomScreenCoordinatorAction { case presentRoomMemberDetails(userID: String) case presentMessageForwarding(forwardingItem: MessageForwardingItem) case presentCallScreen + case presentPinnedEventsTimeline } final class RoomScreenCoordinator: CoordinatorProtocol { @@ -67,7 +68,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol { mediaPlayerProvider: parameters.mediaPlayerProvider, voiceMessageMediaManager: parameters.voiceMessageMediaManager, userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: parameters.appMediator, appSettings: parameters.appSettings, analyticsService: ServiceLocator.shared.analytics) @@ -126,6 +126,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol { composerViewModel.process(roomAction: action) case .displayCallScreen: actionsSubject.send(.presentCallScreen) + case .displayPinnedEventsTimeline: + actionsSubject.send(.presentPinnedEventsTimeline) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 08397682e..ddc309d72 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -33,6 +33,7 @@ enum RoomScreenViewModelAction { case displayLocation(body: String, geoURI: GeoURI, description: String?) case composer(action: RoomScreenComposerAction) case displayCallScreen + case displayPinnedEventsTimeline } enum RoomScreenComposerMode: Equatable { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index d2c457034..c437a4ffe 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -33,7 +33,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private let timelineController: RoomTimelineControllerProtocol private let mediaPlayerProvider: MediaPlayerProviderProtocol private let userIndicatorController: UserIndicatorControllerProtocol - private let networkMonitor: NetworkMonitorProtocol private let appMediator: AppMediatorProtocol private let appSettings: AppSettings private let analyticsService: AnalyticsService @@ -50,7 +49,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private var paginateBackwardsTask: Task? private var paginateForwardsTask: Task? - private var pinnedEventsTimelineProviderSetupTask: Task? private var pinnedEventsTimelineProvider: RoomTimelineProviderProtocol? { didSet { @@ -77,7 +75,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol mediaPlayerProvider: MediaPlayerProviderProtocol, voiceMessageMediaManager: VoiceMessageMediaManagerProtocol, userIndicatorController: UserIndicatorControllerProtocol, - networkMonitor: NetworkMonitorProtocol, appMediator: AppMediatorProtocol, appSettings: AppSettings, analyticsService: AnalyticsService) { @@ -88,7 +85,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol self.analyticsService = analyticsService self.userIndicatorController = userIndicatorController self.appMediator = appMediator - self.networkMonitor = networkMonitor pinnedEventStringBuilder = .pinnedEventStringBuilder(userID: roomProxy.ownUserID) let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider) @@ -148,8 +144,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol state.canJoinCall = permission } } - - setupPinnedEventsTimelineProviderIfNeeded() } // MARK: - Public @@ -230,8 +224,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } state.pinnedEventsBannerState.previousPin() case .viewAllPins: - // TODO: Implement - break + actionsSubject.send(.displayPinnedEventsTimeline) } } @@ -509,12 +502,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } .store(in: &cancellables) - networkMonitor.reachabilityPublisher + appSettings.$pinningEnabled + .combineLatest(appMediator.networkMonitor.reachabilityPublisher) + .filter { $0.0 && $0.1 == .reachable } .receive(on: DispatchQueue.main) - .sink { [weak self] networkState in - if networkState == .reachable { - self?.setupPinnedEventsTimelineProviderIfNeeded() - } + .sink { [weak self] _ in + self?.setupPinnedEventsTimelineProviderIfNeeded() } .store(in: &cancellables) } @@ -534,20 +527,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } private func setupPinnedEventsTimelineProviderIfNeeded() { - guard pinnedEventsTimelineProvider == nil, - pinnedEventsTimelineProviderSetupTask == nil else { + guard pinnedEventsTimelineProvider == nil else { return } - pinnedEventsTimelineProviderSetupTask = Task { [weak self] in - guard let self else { return } - - guard let pinnedEventsTimelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else { - pinnedEventsTimelineProviderSetupTask = nil + Task { + guard let timelineProvider = await roomProxy.pinnedEventsTimeline?.timelineProvider else { return } - self.pinnedEventsTimelineProvider = pinnedEventsTimelineProvider + if pinnedEventsTimelineProvider == nil { + pinnedEventsTimelineProvider = timelineProvider + } } } @@ -953,7 +944,6 @@ extension RoomScreenViewModel { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/RoomScreen/View/ReadReceipts/ReadReceiptsSummaryView.swift b/ElementX/Sources/Screens/RoomScreen/View/ReadReceipts/ReadReceiptsSummaryView.swift index 82433a1ec..165c8aae9 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/ReadReceipts/ReadReceiptsSummaryView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/ReadReceipts/ReadReceiptsSummaryView.swift @@ -58,7 +58,6 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: UserIndicatorControllerMock(), - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 4890bf76d..0655395a9 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -214,7 +214,6 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift index 607eb7d61..e4933ee34 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift @@ -96,7 +96,6 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/HighlightedTimelineItemModifier.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/HighlightedTimelineItemModifier.swift index d599a3289..3152077a9 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/HighlightedTimelineItemModifier.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/HighlightedTimelineItemModifier.swift @@ -101,7 +101,6 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TimelineView.swift index d415bb4c8..5a0b15423 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TimelineView.swift @@ -86,7 +86,6 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift index ae9031a9c..95e72290d 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift @@ -19,7 +19,7 @@ import SwiftUI struct SecureBackupLogoutConfirmationScreenCoordinatorParameters { let secureBackupController: SecureBackupControllerProtocol - let networkMonitor: NetworkMonitorProtocol + let appMediator: AppMediatorProtocol } enum SecureBackupLogoutConfirmationScreenCoordinatorAction { @@ -39,7 +39,7 @@ final class SecureBackupLogoutConfirmationScreenCoordinator: CoordinatorProtocol init(parameters: SecureBackupLogoutConfirmationScreenCoordinatorParameters) { viewModel = SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: parameters.secureBackupController, - networkMonitor: parameters.networkMonitor) + appMediator: parameters.appMediator) } func start() { diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.swift index aa53b0654..bb477ed42 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.swift @@ -21,7 +21,7 @@ typealias SecureBackupLogoutConfirmationScreenViewModelType = StateStoreViewMode class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmationScreenViewModelType, SecureBackupLogoutConfirmationScreenViewModelProtocol { private let secureBackupController: SecureBackupControllerProtocol - private let networkMonitor: NetworkMonitorProtocol + private let appMediator: AppMediatorProtocol // periphery:ignore - auto cancels when reassigned @CancellableTask @@ -32,13 +32,13 @@ class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmat actionsSubject.eraseToAnyPublisher() } - init(secureBackupController: SecureBackupControllerProtocol, networkMonitor: NetworkMonitorProtocol) { + init(secureBackupController: SecureBackupControllerProtocol, appMediator: AppMediatorProtocol) { self.secureBackupController = secureBackupController - self.networkMonitor = networkMonitor + self.appMediator = appMediator super.init(initialViewState: .init(mode: .saveRecoveryKey)) - networkMonitor.reachabilityPublisher + appMediator.networkMonitor.reachabilityPublisher .receive(on: DispatchQueue.main) .sink { [weak self] reachability in guard let self, @@ -75,7 +75,7 @@ class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmat private func attemptLogout() { if state.mode == .saveRecoveryKey { - state.mode = networkMonitor.reachabilityPublisher.value == .reachable ? .backupOngoing : .offline + state.mode = appMediator.networkMonitor.reachabilityPublisher.value == .reachable ? .backupOngoing : .offline keyUploadWaitingTask = Task { var result = await secureBackupController.waitForKeyBackupUpload() diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift index 19cb7465e..8f3c6ac3c 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift @@ -126,7 +126,10 @@ struct SecureBackupLogoutConfirmationScreen_Previews: PreviewProvider, TestableP let networkMonitor = NetworkMonitorMock() networkMonitor.underlyingReachabilityPublisher = CurrentValueSubject(.reachable).asCurrentValuePublisher() + let appMediator = AppMediatorMock() + appMediator.underlyingNetworkMonitor = networkMonitor + return SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: secureBackupController, - networkMonitor: networkMonitor) + appMediator: appMediator) } } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 349ec8a7d..86e5c58fb 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -23,21 +23,38 @@ class RoomProxy: RoomProxyProtocol { private let roomListItem: RoomListItemProtocol private let room: RoomProtocol let timeline: TimelineProxyProtocol + private var innerPinnedEventsTimeline: TimelineProxyProtocol? + private var innerPinnedEventsTimelineTask: Task? var pinnedEventsTimeline: TimelineProxyProtocol? { get async { + // Check if is alrrady available. if let innerPinnedEventsTimeline { return innerPinnedEventsTimeline + // Otherwise check if there is already a task loading it, and wait for it. + } else if let innerPinnedEventsTimelineTask, + let value = await innerPinnedEventsTimelineTask.value { + return value + // Else create and store a new task to load it and wait for it. } else { - do { - let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil, maxEventsToLoad: 100), isLive: false) - await timeline.subscribeForUpdates() - innerPinnedEventsTimeline = timeline - return timeline - } catch { - MXLog.error("Failed creating pinned events timeline with error: \(error)") - return nil + let task = Task { [weak self] in + guard let self else { + return nil + } + + do { + let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil, maxEventsToLoad: 100), isLive: false) + await timeline.subscribeForUpdates() + innerPinnedEventsTimeline = timeline + return timeline + } catch { + MXLog.error("Failed creating pinned events timeline with error: \(error)") + return nil + } } + + innerPinnedEventsTimelineTask = task + return await task.value } } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index f162ae6c4..77f5466fb 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -53,6 +53,15 @@ enum TimelineItemProxy { self = .unknown(item) } } + + var isEvent: Bool { + switch self { + case .event: + return true + default: + return false + } + } } /// The delivery status for the item. diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index bdfa4ef57..c987e708f 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -21,6 +21,7 @@ import MatrixRustSDK class UserSessionStore: UserSessionStoreProtocol { private let keychainController: KeychainControllerProtocol private let appSettings: AppSettings + private let networkMonitor: NetworkMonitorProtocol private let appHooks: AppHooks private let matrixSDKStateKey = "matrix-sdk-state" @@ -31,10 +32,14 @@ class UserSessionStore: UserSessionStoreProtocol { var clientSessionDelegate: ClientSessionDelegate { keychainController } - init(keychainController: KeychainControllerProtocol, appSettings: AppSettings, appHooks: AppHooks) { + init(keychainController: KeychainControllerProtocol, + appSettings: AppSettings, + appHooks: AppHooks, + networkMonitor: NetworkMonitorProtocol) { self.keychainController = keychainController self.appSettings = appSettings self.appHooks = appHooks + self.networkMonitor = networkMonitor } /// Deletes all data stored in the shared container and keychain @@ -146,8 +151,8 @@ class UserSessionStore: UserSessionStoreProtocol { private func setupProxyForClient(_ client: Client) async -> ClientProxyProtocol { await ClientProxy(client: client, - networkMonitor: ServiceLocator.shared.networkMonitor, - appSettings: ServiceLocator.shared.settings) + networkMonitor: networkMonitor, + appSettings: appSettings) } private func deleteSessionDirectory(for credentials: KeychainCredentials) { diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 4583816eb..81eb82863 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -47,7 +47,6 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate AppSettings.resetAllSettings() ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(bugReportService: BugReportServiceMock()) - ServiceLocator.shared.register(networkMonitor: NetworkMonitorMock.default) let analyticsClient = AnalyticsClientMock() analyticsClient.isRunning = false diff --git a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift index 94f541bec..689d67258 100644 --- a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift +++ b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift @@ -27,7 +27,6 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol { AppSettings.resetAllSettings() ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(bugReportService: BugReportServiceMock()) - ServiceLocator.shared.register(networkMonitor: NetworkMonitorMock.default) let analyticsClient = AnalyticsClientMock() analyticsClient.isRunning = false diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..576e6a047 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:792b9704be2c179392b93540fd3efd20697921f1fa9979bd9c6297ab180e7d6e +size 95185 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..2695fab3f --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa12b52b37641df2270df083d64a8266212b4d95960e70b052a002baef129201 +size 109865 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..769207d65 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c520c10a03670bcf1a84cb2521a402a076cd6edd27fc32efffb8da955c159824 +size 56615 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..9618a5053 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_pinnedEventsTimelineScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57970025bc4c1ed70433696bac8bc18afd57fda4c6f96b538a0bb322946dfdb2 +size 76367 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.DM-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.DM-Room.png index a0cc21838..1e4ca1fce 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.DM-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.DM-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:779bfa3a4f558fe21ee44e2196a9e0f45a9a28dc8ec6b2fc1fce615af096a4a5 -size 195022 +oid sha256:426e4bce963193df6ce6be11ccd1a9ba1a22fa064aae84ebe96ecb92a164b507 +size 196225 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Generic-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Generic-Room.png index cd6a76911..be6ff1bc3 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Generic-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Generic-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00a9eec7fbc1be3ce6e370b005b047fc24555fb7c7db3f82d56a9c15dbe220f0 -size 170562 +oid sha256:162f3f7d89b385d26a3dd7e83c560e148001271b94458f1c97ab3bc5ba04735e +size 173481 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Simple-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Simple-Room.png index 0ffff1e8e..10945db8b 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Simple-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-en-GB.Simple-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55ed8c53cf2a613fc6e991dbba38c152901b7c42776c19bdf1a2ada0bebec0aa -size 140779 +oid sha256:299de08d08e1a8e31555b2cff1f2f74c3724a4f396ac8edff3004ba3500aa78b +size 146568 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.DM-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.DM-Room.png index b09d98acf..a9328a97c 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.DM-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.DM-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df2215e8903df4b8f9a6d64dd376383a3047aa3793fd148fe14e29f9a554565a -size 199745 +oid sha256:1e5c945f464ebacef94fe9d71da03678b2148f5658c31fcb79d500967ac5ba0a +size 200975 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Generic-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Generic-Room.png index 05295fa5d..259cd0e91 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Generic-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Generic-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:359d52ea5495068199d5ab9cc93d68d1b92ff94e458636339964498d027fa221 -size 176341 +oid sha256:d3e4a05c141803ba5c7bdb7fee4c4059d1b28d88614e32c842cd6a5c8b711feb +size 179948 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Simple-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Simple-Room.png index 676eb78ff..0c6c80750 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Simple-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPad-pseudo.Simple-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:204fd0643e651f6f8106dcc7e7cb4039cdaeaf657b3ed564a2e06e04a531a221 -size 148488 +oid sha256:a58443a60e339c3041d3c154f056559f558aeabefff42375cd11a1c8aa602c8b +size 155595 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.DM-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.DM-Room.png index a5464493f..518a4f1e8 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.DM-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.DM-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a2e23d8128cbb1f5ea2a2a7004505ae5eb8cca4534dcffbdb167edb9f09e974 -size 136048 +oid sha256:9128fc4e3140862b04941fb298b5b352b732556902862f52f78e3c06c1acc00e +size 139620 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Generic-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Generic-Room.png index 9c0eeacfb..72e8f6ef6 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Generic-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Generic-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0befcf54dfa879766c12a1b4f1e8b0c78c307b251fedda37d96d8c12ea926c34 -size 109268 +oid sha256:2b181035e41897519ea7edd67feabdd46727000b7028bf3e2d35fa54b5d90a23 +size 111799 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Simple-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Simple-Room.png index a34afd542..0f13aae40 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Simple-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-en-GB.Simple-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa3c1eac0d5b3b7bd862a4677d83d920a98ba96fec19248d2976d7006537117f -size 79268 +oid sha256:a4a39dd36ae824a1de6f05a815e3456f587c29f9b38e8241687d39af5de34aef +size 84853 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.DM-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.DM-Room.png index fd817ab6b..4a7da0711 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.DM-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.DM-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fde3cd7e68cdeaa626e3999c3de3bcd42600e0239bf110e1fb4e424a0cbfe41 -size 145812 +oid sha256:3d7488c400a906d44d246e1f8e2705f127aa254f3f1639c46e6206b072a9fab2 +size 152011 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Generic-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Generic-Room.png index 45bf929ee..17669ea56 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Generic-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Generic-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d01812b6261a3cc811ec04709303a3f3a8d88d33aa634cd0ab52d1db1a95cd2e -size 123476 +oid sha256:f94ecc74654a4a51b6cc927d7fabf70f6465a433811447ce1e1bb0a80ab87cba +size 127605 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Simple-Room.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Simple-Room.png index 8af14a420..01de4402c 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Simple-Room.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDetailsScreen-iPhone-15-pseudo.Simple-Room.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f23f3a10ee4df63d4b4e98b8f58c915bb0d06a27202e9a9e3b1343dbddeef91 -size 105860 +oid sha256:5c9805e5c3f1fc3cf5182ba6dbe812bf85835c62b1caedeb2b8b9b96773e8c21 +size 106502 diff --git a/UnitTests/Sources/PillContextTests.swift b/UnitTests/Sources/PillContextTests.swift index 331d5ca3c..3ddbd4f78 100644 --- a/UnitTests/Sources/PillContextTests.swift +++ b/UnitTests/Sources/PillContextTests.swift @@ -32,7 +32,6 @@ class PillContextTests: XCTestCase { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) @@ -61,7 +60,6 @@ class PillContextTests: XCTestCase { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) @@ -83,7 +81,6 @@ class PillContextTests: XCTestCase { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index e65c449ff..2759d89e1 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -39,7 +39,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxyMock, - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) AppSettings.resetAllSettings() } @@ -53,7 +55,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) let deferred = deferFulfillment(context.$viewState) { state in state.bindings.leaveRoomAlertItem != nil } @@ -74,7 +78,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) let deferred = deferFulfillment(context.$viewState) { state in state.bindings.leaveRoomAlertItem != nil } @@ -96,7 +102,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) context.send(viewAction: .processTapLeave) XCTAssertEqual(context.leaveRoomAlertItem?.state, .empty) @@ -148,7 +156,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) let deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -170,7 +180,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -203,7 +215,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -235,7 +249,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -268,7 +284,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) var deferred = deferFulfillment(viewModel.context.$viewState) { state in state.dmRecipient != nil @@ -302,7 +320,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -318,7 +338,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -353,7 +375,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -375,7 +399,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -397,7 +423,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -416,7 +444,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -435,7 +465,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()), - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -452,7 +484,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase { analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, notificationSettingsProxy: notificationSettingsProxyMock, - attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder())) + attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings) var deferred = deferFulfillment(context.$viewState) { state in state.notificationSettingsState.isError diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index 1ca48bd74..b547d928e 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -351,7 +351,6 @@ class RoomScreenViewModelTests: XCTestCase { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: userIndicatorControllerMock, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) @@ -376,7 +375,6 @@ class RoomScreenViewModelTests: XCTestCase { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: userIndicatorControllerMock, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics) @@ -401,7 +399,6 @@ class RoomScreenViewModelTests: XCTestCase { mediaPlayerProvider: MediaPlayerProviderMock(), voiceMessageMediaManager: VoiceMessageMediaManagerMock(), userIndicatorController: userIndicatorControllerMock, - networkMonitor: ServiceLocator.shared.networkMonitor, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics)