From abe1df25240cb6ff607adc39087f91fc11ede0b2 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 23 Jun 2023 09:56:22 +0300 Subject: [PATCH] Removed unused swipe gesture (+3 squashed commits) Squashed commits: [d64bb3bb] Stop using the ServiceLocator directly in the ScreenTrackerViewModifier [37c46ab9] Rename Analytics to AnalyticsService [8852a371] #920 - Cleanup ServiceLocator usages --- ElementX.xcodeproj/project.pbxproj | 16 ++-- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Sources/Application/AppCoordinator.swift | 28 +++++-- .../Sources/Application/ServiceLocator.swift | 4 +- .../RoomFlowCoordinator.swift | 13 +++- .../UserSessionFlowCoordinator.swift | 4 +- .../Mocks/Generated/GeneratedMocks.swift | 18 +++-- .../Analytics/ScreenTrackerViewModifier.swift | 4 +- ElementX/Sources/Other/Extensions/Swipe.swift | 34 --------- .../Extensions/UNNotificationContent.swift | 1 + .../HTMLParsing/AttributedStringBuilder.swift | 7 +- ElementX/Sources/Other/PermalinkBuilder.swift | 24 +++--- .../SwiftUI/Animation/ShimmerModifier.swift | 6 +- .../SwiftUI/Views/MatrixUserShareLink.swift | 3 +- .../AnalyticsPromptScreenCoordinator.swift | 10 ++- .../AnalyticsPromptScreenViewModel.swift | 4 +- .../View/AnalyticsPromptScreen.swift | 2 +- .../AuthenticationCoordinator.swift | 45 ++++++----- .../LoginScreen/LoginScreenCoordinator.swift | 15 ++-- .../LoginScreen/LoginScreenViewModel.swift | 7 +- .../LoginScreen/View/LoginScreen.swift | 8 +- .../OIDCAuthenticationPresenter.swift | 6 +- .../MockServerSelectionScreenState.swift | 5 ++ .../ServerSelectionScreenCoordinator.swift | 1 + .../ServerSelectionScreenModels.swift | 22 ++++-- .../ServerSelectionScreenViewModel.swift | 10 ++- .../SoftLogoutScreenCoordinator.swift | 15 ++-- .../CreateRoom/CreateRoomCoordinator.swift | 5 +- .../CreateRoom/CreateRoomViewModel.swift | 16 ++-- .../CreateRoom/View/CreateRoomScreen.swift | 12 ++- .../HomeScreen/HomeScreenCoordinator.swift | 4 +- .../HomeScreen/HomeScreenViewModel.swift | 29 ++++--- .../Screens/HomeScreen/View/HomeScreen.swift | 6 +- .../HomeScreen/View/HomeScreenRoomCell.swift | 6 +- .../InviteUsersScreenCoordinator.swift | 1 + .../InviteUsersScreenViewModel.swift | 8 +- .../View/InviteUsersScreen.swift | 7 +- .../InvitesScreenCoordinator.swift | 5 +- .../InvitesScreenViewModel.swift | 28 +++++-- .../InvitesScreen/View/InvitesScreen.swift | 10 ++- .../RoomDetailsScreenCoordinator.swift | 10 ++- .../RoomDetailsScreenViewModel.swift | 10 ++- .../View/RoomDetailsScreen.swift | 6 +- .../RoomMembersListScreenCoordinator.swift | 3 +- .../RoomMembersListScreenViewModel.swift | 12 ++- .../View/RoomMembersListScreen.swift | 3 +- .../RoomMembersListScreenMemberCell.swift | 3 +- .../RoomScreen/RoomScreenCoordinator.swift | 5 +- .../RoomScreen/RoomScreenViewModel.swift | 32 +++++--- .../View/RoomAttachmentPicker.swift | 5 +- .../RoomScreen/View/RoomHeaderView.swift | 10 ++- .../Screens/RoomScreen/View/RoomScreen.swift | 5 +- .../TimelineReadReceiptsView.swift | 5 +- .../View/Timeline/FormattedBodyText.swift | 2 +- .../RoomScreen/View/TimelineView.swift | 5 +- .../AnalyticsSettingsScreenCoordinator.swift | 10 ++- .../AnalyticsSettingsScreenViewModel.swift | 20 +++-- .../View/AnalyticsSettingsScreen.swift | 6 +- .../DeveloperOptionsScreenCoordinator.swift | 2 +- .../DeveloperOptionsScreenViewModel.swift | 9 ++- .../View/DeveloperOptionsScreen.swift | 2 +- .../SettingsScreenCoordinator.swift | 5 +- .../SettingsScreenViewModel.swift | 13 ++-- .../SettingsScreen/View/SettingsScreen.swift | 3 +- .../StartChatScreenCoordinator.swift | 6 +- .../StartChatScreenViewModel.swift | 21 ++++-- .../View/StartChatScreen.swift | 6 +- .../Analytics/AnalyticsClientProtocol.swift | 2 +- ...Analytics.swift => AnalyticsService.swift} | 28 ++++--- .../Analytics/Helpers/Analytics+SwiftUI.swift | 28 +++++++ .../Analytics/PHGPostHogConfiguration.swift | 3 +- .../Analytics/PostHogAnalyticsClient.swift | 4 +- .../AuthenticationServiceProxy.swift | 19 ++--- .../Services/BugReport/BugReportService.swift | 4 +- .../Sources/Services/Client/ClientProxy.swift | 10 ++- .../Manager/NotificationManager.swift | 17 +++-- .../RoomMember/RoomMemberProxyProtocol.swift | 3 +- .../Sources/Services/Room/RoomProxy.swift | 1 - .../Services/Room/RoomProxyProtocol.swift | 6 +- .../Fixtures/RoomTimelineItemFixtures.swift | 2 +- .../RoomTimelineController.swift | 7 +- .../RoomTimelineControllerFactory.swift | 3 +- .../UserSession/UserSessionStore.swift | 4 +- .../UITests/UITestsAppCoordinator.swift | 46 ++++++++---- .../UnitTests/UnitTestsAppCoordinator.swift | 4 +- ...nalyticsSettingsScreenViewModelTests.swift | 12 +-- UnitTests/Sources/AnalyticsTests.swift | 14 ++-- .../AttributedStringBuilderTests.swift | 2 +- UnitTests/Sources/AttributedStringTests.swift | 3 +- UnitTests/Sources/BugReportServiceTests.swift | 2 + .../Sources/CreateRoomViewModelTests.swift | 6 +- .../Sources/HomeScreenViewModelTests.swift | 6 +- .../Sources/InviteUsersViewModelTests.swift | 1 + .../Sources/InvitesScreenViewModelTests.swift | 5 +- UnitTests/Sources/LoginViewModelTests.swift | 2 +- .../NotificationManagerTests.swift | 2 +- UnitTests/Sources/PermalinkBuilderTests.swift | 26 +++---- .../Sources/RoomDetailsViewModelTests.swift | 75 +++++++++++++++---- .../Sources/RoomFlowCoordinatorTests.swift | 2 + .../RoomMembersListViewModelTests.swift | 3 +- .../Sources/RoomScreenViewModelTests.swift | 52 ++++++++++--- .../ServerSelectionViewModelTests.swift | 4 +- .../Sources/SettingsViewModelTests.swift | 2 +- .../Sources/StartChatViewModelTests.swift | 6 +- 104 files changed, 720 insertions(+), 366 deletions(-) delete mode 100644 ElementX/Sources/Other/Extensions/Swipe.swift rename ElementX/Sources/Services/Analytics/{Analytics.swift => AnalyticsService.swift} (87%) create mode 100644 ElementX/Sources/Services/Analytics/Helpers/Analytics+SwiftUI.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 011549eb3..654d0d029 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; }; 14E99D27628B1A6F0CB46FEA /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */; }; 152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */; }; + 155063E980E763D4910EA3CF /* Analytics+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */; }; 1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4549FCB53F43DB0B278374BC /* TemplateScreen.swift */; }; 157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */; }; 158A2D528CC78C4E7A8ED608 /* MockRoomTimelineControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71556206CD5E8B1F53F07178 /* MockRoomTimelineControllerFactory.swift */; }; @@ -155,6 +156,7 @@ 3B0F9B57D25B07E66F15762A /* MediaUploadPreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */; }; 3B28408450BCAED911283AA2 /* UserPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FA991289149D31F4286747 /* UserPreference.swift */; }; 3C549A0BF39F8A854D45D9FD /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; }; + 3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */; }; 3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; }; 3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861FE915C7B5466E6962BBA /* StartChatScreen.swift */; }; 3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893777A4997BBDB68079D4F5 /* ArrayTests.swift */; }; @@ -179,7 +181,6 @@ 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; }; 44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */; }; 447E8580A0A2569E32529E17 /* MockRoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */; }; - 44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */; }; 46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565F1B2B300597C616B37888 /* FullscreenDialog.swift */; }; @@ -442,7 +443,6 @@ A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; }; A2A5AB2E8B3F5CA769E531FA /* TextBasedRoomTimelineViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E47F18A9A077E351CEA10D4 /* TextBasedRoomTimelineViewProtocol.swift */; }; A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; }; - A371629728E597C5FCA3C2B2 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FC861755C6388F62B9280A /* Analytics.swift */; }; A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287FC98AF2664EAD79C0D902 /* UIDevice.swift */; }; A3A7A05E8F9B7EB0E1A09A2A /* SoftLogoutScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05596E4A11A8C9346E9E54AE /* SoftLogoutScreenCoordinator.swift */; }; A3E390675E9730C176B59E1B /* ImageProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */; }; @@ -976,6 +976,7 @@ 5351EBD7A0B9610548E4B7B2 /* EncryptedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedRoomTimelineItem.swift; sourceTree = ""; }; 536E72DCBEEC4A1FE66CFDCE /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 542D4F49FABA056DEEEB3400 /* RustTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustTracing.swift; sourceTree = ""; }; + 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = ""; }; 54C4E7B46099462F12000C91 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = ""; }; 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSender.swift; sourceTree = ""; }; 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = ""; }; @@ -1056,7 +1057,6 @@ 71D52BAA5BADB06E5E8C295D /* Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = ""; }; 7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; - 73FC861755C6388F62B9280A /* Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; 7475C5AE20BA896930907EA8 /* AudioRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItemContent.swift; sourceTree = ""; }; 748AE77AC3B0A01223033B87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenCoordinator.swift; sourceTree = ""; }; @@ -1172,7 +1172,6 @@ A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; - A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swipe.swift; sourceTree = ""; }; AA19C32BD97F45847724E09A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Untranslated.strings; sourceTree = ""; }; AA85B02533375D19744EAA46 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = ""; }; AAC9344689121887B74877AF /* UnitTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1208,6 +1207,7 @@ B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = ""; }; B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = ""; }; B48B7AD4908C5C374517B892 /* MapAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MapAssets.xcassets; sourceTree = ""; }; + B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.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 = ""; }; @@ -1884,6 +1884,7 @@ 3A304097A59704AC9B869EC6 /* Helpers */ = { isa = PBXGroup; children = ( + B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */, CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */, 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */, ); @@ -1992,7 +1993,6 @@ 7310D8DFE01AF45F0689C3AA /* Publisher.swift */, 584A61D9C459FAFEF038A7C0 /* Section.swift */, 40B21E611DADDEF00307E7AC /* String.swift */, - A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */, A40C19719687984FD9478FBE /* Task.swift */, 287FC98AF2664EAD79C0D902 /* UIDevice.swift */, BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */, @@ -2042,11 +2042,11 @@ 4BF8D11D9ED15CFC373D0119 /* Analytics */ = { isa = PBXGroup; children = ( - 73FC861755C6388F62B9280A /* Analytics.swift */, E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */, D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */, 57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */, FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */, + 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */, A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */, 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */, 3A304097A59704AC9B869EC6 /* Helpers */, @@ -3971,7 +3971,7 @@ 4219391CD2351E410554B3E8 /* AggregratedReaction.swift in Sources */, 64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */, 39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */, - A371629728E597C5FCA3C2B2 /* Analytics.swift in Sources */, + 155063E980E763D4910EA3CF /* Analytics+SwiftUI.swift in Sources */, F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */, 54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */, 8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */, @@ -3982,6 +3982,7 @@ 0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */, D4ACF3276F5D0DA28D4028C9 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */, D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */, + 3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */, 020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */, 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */, 7C6376192F578E0BA801BFEC /* AnalyticsSettingsScreenModels.swift in Sources */, @@ -4354,7 +4355,6 @@ 2F94054F50E312AF30BE07F3 /* String.swift in Sources */, A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */, 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */, - 44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */, E290C78E7F09F47FD2662986 /* Task.swift in Sources */, 1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */, 47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */, diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4f4411b1f..be096756c 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -181,7 +181,7 @@ { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", "state" : { "revision" : "cef5b3f6f11781dd4591bdd1dd0a3d22bd609334", "version" : "1.11.0" diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index e072d2470..fc0d884e5 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -79,7 +79,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService) - notificationManager = NotificationManager() + notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(), + appSettings: ServiceLocator.shared.settings) notificationManager.delegate = self notificationManager.start() @@ -117,7 +118,10 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, } func toPresentable() -> AnyView { - ServiceLocator.shared.userIndicatorController.toPresentable() + AnyView( + ServiceLocator.shared.userIndicatorController.toPresentable() + .environment(\.analyticsService, ServiceLocator.shared.analytics) + ) } // MARK: - AuthenticationCoordinatorDelegate @@ -194,8 +198,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(networkMonitor: NetworkMonitor()) ServiceLocator.shared.register(bugReportService: BugReportService(withBaseURL: ServiceLocator.shared.settings.bugReportServiceBaseURL, - sentryURL: ServiceLocator.shared.settings.bugReportSentryURL)) - ServiceLocator.shared.register(analytics: Analytics(client: PostHogAnalyticsClient())) + sentryURL: ServiceLocator.shared.settings.bugReportSentryURL, + applicationId: ServiceLocator.shared.settings.bugReportApplicationId, + maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize)) + ServiceLocator.shared.register(analytics: AnalyticsService(client: PostHogAnalyticsClient(), + appSettings: ServiceLocator.shared.settings, + bugReportService: ServiceLocator.shared.bugReportService)) } /// Perform any required migrations for the app to function correctly. @@ -270,9 +278,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, private func startAuthentication() { let authenticationNavigationStackCoordinator = NavigationStackCoordinator() - let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore) + let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, appSettings: ServiceLocator.shared.settings) authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService, - navigationStackCoordinator: authenticationNavigationStackCoordinator) + navigationStackCoordinator: authenticationNavigationStackCoordinator, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) authenticationCoordinator?.delegate = self authenticationCoordinator?.start() @@ -296,12 +307,13 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, userDisplayName: displayName, deviceID: userSession.deviceID) - let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore) + let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, appSettings: ServiceLocator.shared.settings) _ = await authenticationService.configure(for: userSession.homeserver) let parameters = SoftLogoutScreenCoordinatorParameters(authenticationService: authenticationService, credentials: credentials, - keyBackupNeeded: false) + keyBackupNeeded: false, + userIndicatorController: ServiceLocator.shared.userIndicatorController) let coordinator = SoftLogoutScreenCoordinator(parameters: parameters) coordinator.callback = { result in switch result { diff --git a/ElementX/Sources/Application/ServiceLocator.swift b/ElementX/Sources/Application/ServiceLocator.swift index e31cdcb32..d343cf5b8 100644 --- a/ElementX/Sources/Application/ServiceLocator.swift +++ b/ElementX/Sources/Application/ServiceLocator.swift @@ -39,9 +39,9 @@ class ServiceLocator { self.networkMonitor = networkMonitor } - private(set) var analytics: Analytics! + private(set) var analytics: AnalyticsService! - func register(analytics: Analytics) { + func register(analytics: AnalyticsService) { self.analytics = analytics } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 82b96d820..430430230 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -29,6 +29,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { private let navigationStackCoordinator: NavigationStackCoordinator private let navigationSplitCoordinator: NavigationSplitCoordinator private let emojiProvider: EmojiProviderProtocol + private let appSettings: AppSettings + private let analytics: AnalyticsService private let userIndicatorController: UserIndicatorControllerProtocol private let stateMachine: StateMachine = .init(state: .initial) @@ -48,12 +50,16 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator: NavigationStackCoordinator, navigationSplitCoordinator: NavigationSplitCoordinator, emojiProvider: EmojiProviderProtocol, + appSettings: AppSettings, + analytics: AnalyticsService, userIndicatorController: UserIndicatorControllerProtocol) { self.userSession = userSession self.roomTimelineControllerFactory = roomTimelineControllerFactory self.navigationStackCoordinator = navigationStackCoordinator self.navigationSplitCoordinator = navigationSplitCoordinator self.emojiProvider = emojiProvider + self.appSettings = appSettings + self.analytics = analytics self.userIndicatorController = userIndicatorController setupStateMachine() @@ -273,7 +279,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let timelineItemFactory = RoomTimelineItemFactory(userID: userId, mediaProvider: userSession.mediaProvider, - attributedStringBuilder: AttributedStringBuilder(), + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userId)) let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(userId: userId, @@ -282,7 +288,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { mediaProvider: userSession.mediaProvider) self.timelineController = timelineController - ServiceLocator.shared.analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace) + analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace) let parameters = RoomScreenCoordinatorParameters(roomProxy: roomProxy, timelineController: timelineController, @@ -365,7 +371,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, mediaProvider: userSession.mediaProvider, - userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy)) + userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy), + userIndicatorController: userIndicatorController) let coordinator = RoomDetailsScreenCoordinator(parameters: params) coordinator.callback = { [weak self] action in switch action { diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 8faa5615c..7495701a8 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -60,6 +60,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator: detailNavigationStackCoordinator, navigationSplitCoordinator: navigationSplitCoordinator, emojiProvider: EmojiProvider(), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController) setupStateMachine() @@ -180,7 +182,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { // swiftlint:disable:next cyclomatic_complexity private func presentHomeScreen() { let parameters = HomeScreenCoordinatorParameters(userSession: userSession, - attributedStringBuilder: AttributedStringBuilder(), + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL), bugReportService: bugReportService, navigationStackCoordinator: detailNavigationStackCoordinator, selectedRoomPublisher: selectedRoomSubject.asCurrentValuePublisher()) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 5a8b2e57a..45ddc6943 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -16,15 +16,19 @@ class AnalyticsClientMock: AnalyticsClientProtocol { //MARK: - start - var startCallsCount = 0 - var startCalled: Bool { - return startCallsCount > 0 + var startAnalyticsConfigurationCallsCount = 0 + var startAnalyticsConfigurationCalled: Bool { + return startAnalyticsConfigurationCallsCount > 0 } - var startClosure: (() -> Void)? + var startAnalyticsConfigurationReceivedAnalyticsConfiguration: AnalyticsConfiguration? + var startAnalyticsConfigurationReceivedInvocations: [AnalyticsConfiguration] = [] + var startAnalyticsConfigurationClosure: ((AnalyticsConfiguration) -> Void)? - func start() { - startCallsCount += 1 - startClosure?() + func start(analyticsConfiguration: AnalyticsConfiguration) { + startAnalyticsConfigurationCallsCount += 1 + startAnalyticsConfigurationReceivedAnalyticsConfiguration = analyticsConfiguration + startAnalyticsConfigurationReceivedInvocations.append(analyticsConfiguration) + startAnalyticsConfigurationClosure?(analyticsConfiguration) } //MARK: - reset diff --git a/ElementX/Sources/Other/Analytics/ScreenTrackerViewModifier.swift b/ElementX/Sources/Other/Analytics/ScreenTrackerViewModifier.swift index f23e49bdc..b33629abf 100644 --- a/ElementX/Sources/Other/Analytics/ScreenTrackerViewModifier.swift +++ b/ElementX/Sources/Other/Analytics/ScreenTrackerViewModifier.swift @@ -18,13 +18,15 @@ import SwiftUI /// `ScreenTrackerViewModifier` is a helper class used to track PostHog screen from SwiftUI screens. struct ScreenTrackerViewModifier: ViewModifier { + @Environment(\.analyticsService) private var analyticsService + let screen: AnalyticsScreen @ViewBuilder func body(content: Content) -> some View { content .onAppear { - ServiceLocator.shared.analytics.track(screen: screen) + analyticsService.track(screen: screen) } } } diff --git a/ElementX/Sources/Other/Extensions/Swipe.swift b/ElementX/Sources/Other/Extensions/Swipe.swift deleted file mode 100644 index 2cbd86c43..000000000 --- a/ElementX/Sources/Other/Extensions/Swipe.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// 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 -import SwiftUI - -extension View { - func onSwipeGesture(minimumDistance: CGFloat, - up: @escaping (() -> Void) = { }, - down: @escaping (() -> Void) = { }, - left: @escaping (() -> Void) = { }, - right: @escaping (() -> Void) = { }) -> some View { - gesture(DragGesture(minimumDistance: minimumDistance, coordinateSpace: .local) - .onEnded { value in - if value.translation.width < 0 { left() } - if value.translation.width > 0 { right() } - if value.translation.height < 0 { up() } - if value.translation.height > 0 { down() } - }) - } -} diff --git a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift index 10001200c..3d9adbf10 100644 --- a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift +++ b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift @@ -101,6 +101,7 @@ extension UNMutableNotificationContent { return self } + // swiftlint:disable:next function_body_length func addSenderIcon(using mediaProvider: MediaProviderProtocol?, senderID: String, senderName: String, diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift index 777c28246..836de8f95 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift @@ -21,6 +21,11 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { private let temporaryBlockquoteMarkingColor = UIColor.magenta private let temporaryCodeBlockMarkingColor = UIColor.cyan private let linkColor = UIColor.blue + private let permalinkBaseURL: URL + + init(permalinkBaseURL: URL) { + self.permalinkBaseURL = permalinkBaseURL + } func fromPlain(_ string: String?) -> AttributedString? { guard let string else { @@ -169,7 +174,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in if value != nil { if let url = value as? URL { - switch PermalinkBuilder.detectPermalink(in: url) { + switch PermalinkBuilder.detectPermalink(in: url, baseURL: permalinkBaseURL) { case .userIdentifier(let identifier): attributedString.addAttributes([.MatrixUserID: identifier], range: range) case .roomIdentifier(let identifier): diff --git a/ElementX/Sources/Other/PermalinkBuilder.swift b/ElementX/Sources/Other/PermalinkBuilder.swift index e468f928c..c24fae060 100644 --- a/ElementX/Sources/Other/PermalinkBuilder.swift +++ b/ElementX/Sources/Other/PermalinkBuilder.swift @@ -39,8 +39,8 @@ enum PermalinkBuilder { return charset }() - static func detectPermalink(in url: URL) -> PermalinkType? { - guard url.absoluteString.hasPrefix(ServiceLocator.shared.settings.permalinkBaseURL.absoluteString) else { + static func detectPermalink(in url: URL, baseURL: URL) -> PermalinkType? { + guard url.absoluteString.hasPrefix(baseURL.absoluteString) else { return nil } @@ -78,12 +78,12 @@ enum PermalinkBuilder { return nil } - static func permalinkTo(userIdentifier: String) throws -> URL { + static func permalinkTo(userIdentifier: String, baseURL: URL) throws -> URL { guard MatrixEntityRegex.isMatrixUserIdentifier(userIdentifier) else { throw PermalinkBuilderError.invalidUserIdentifier } - let urlString = "\(ServiceLocator.shared.settings.permalinkBaseURL)/#/\(userIdentifier)" + let urlString = "\(baseURL)/#/\(userIdentifier)" guard let url = URL(string: urlString) else { throw PermalinkBuilderError.failedConstructingURL @@ -92,23 +92,23 @@ enum PermalinkBuilder { return url } - static func permalinkTo(roomIdentifier: String) throws -> URL { + static func permalinkTo(roomIdentifier: String, baseURL: URL) throws -> URL { guard MatrixEntityRegex.isMatrixRoomIdentifier(roomIdentifier) else { throw PermalinkBuilderError.invalidRoomIdentifier } - return try permalinkTo(roomIdentifierOrAlias: roomIdentifier) + return try permalinkTo(roomIdentifierOrAlias: roomIdentifier, baseURL: baseURL) } - static func permalinkTo(roomAlias: String) throws -> URL { + static func permalinkTo(roomAlias: String, baseURL: URL) throws -> URL { guard MatrixEntityRegex.isMatrixRoomAlias(roomAlias) else { throw PermalinkBuilderError.invalidRoomAlias } - return try permalinkTo(roomIdentifierOrAlias: roomAlias) + return try permalinkTo(roomIdentifierOrAlias: roomAlias, baseURL: baseURL) } - static func permalinkTo(eventIdentifier: String, roomIdentifier: String) throws -> URL { + static func permalinkTo(eventIdentifier: String, roomIdentifier: String, baseURL: URL) throws -> URL { guard MatrixEntityRegex.isMatrixEventIdentifier(eventIdentifier) else { throw PermalinkBuilderError.invalidEventIdentifier } @@ -121,7 +121,7 @@ enum PermalinkBuilder { throw PermalinkBuilderError.failedAddingPercentEncoding } - let urlString = "\(ServiceLocator.shared.settings.permalinkBaseURL)/#/\(roomId)/\(eventId)" + let urlString = "\(baseURL)/#/\(roomId)/\(eventId)" guard let url = URL(string: urlString) else { throw PermalinkBuilderError.failedConstructingURL @@ -132,12 +132,12 @@ enum PermalinkBuilder { // MARK: - Private - private static func permalinkTo(roomIdentifierOrAlias: String) throws -> URL { + private static func permalinkTo(roomIdentifierOrAlias: String, baseURL: URL) throws -> URL { guard let identifier = roomIdentifierOrAlias.addingPercentEncoding(withAllowedCharacters: uriComponentCharacterSet) else { throw PermalinkBuilderError.failedAddingPercentEncoding } - let urlString = "\(ServiceLocator.shared.settings.permalinkBaseURL)/#/\(identifier)" + let urlString = "\(baseURL)/#/\(identifier)" guard let url = URL(string: urlString) else { throw PermalinkBuilderError.failedConstructingURL diff --git a/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift b/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift index 1b29fa4e3..57ad49200 100644 --- a/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift +++ b/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift @@ -73,8 +73,10 @@ extension View { struct ShimmerOverlay_Previews: PreviewProvider { static let viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: MockClientProxy(userID: ""), mediaProvider: MockMediaProvider()), - attributedStringBuilder: AttributedStringBuilder(), - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher()) + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL), + selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + userIndicatorController: ServiceLocator.shared.userIndicatorController) static var previews: some View { VStack { diff --git a/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift b/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift index 64239dd78..cb63d63e9 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/MatrixUserShareLink.swift @@ -22,7 +22,8 @@ struct MatrixUserShareLink: View { init(userID: String, @ViewBuilder label: () -> Label) { self.label = label() - permalink = try? PermalinkBuilder.permalinkTo(userIdentifier: userID) + permalink = try? PermalinkBuilder.permalinkTo(userIdentifier: userID, + baseURL: ServiceLocator.shared.settings.permalinkBaseURL) } var body: some View { diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift b/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift index f53243759..d9f619f93 100644 --- a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift +++ b/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift @@ -17,12 +17,14 @@ import SwiftUI final class AnalyticsPromptScreenCoordinator: CoordinatorProtocol { + private let analytics: AnalyticsService private var viewModel: AnalyticsPromptScreenViewModel var callback: (@MainActor () -> Void)? - init() { - viewModel = AnalyticsPromptScreenViewModel() + init(analytics: AnalyticsService, termsURL: URL) { + self.analytics = analytics + viewModel = AnalyticsPromptScreenViewModel(termsURL: termsURL) } // MARK: - Public @@ -34,11 +36,11 @@ final class AnalyticsPromptScreenCoordinator: CoordinatorProtocol { switch result { case .enable: MXLog.info("Enable Analytics") - ServiceLocator.shared.analytics.optIn() + analytics.optIn() self.callback?() case .disable: MXLog.info("Disable Analytics") - ServiceLocator.shared.analytics.optOut() + analytics.optOut() self.callback?() } } diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift b/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift index 1c15f4f9e..e2ece39fb 100644 --- a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift +++ b/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift @@ -23,8 +23,8 @@ class AnalyticsPromptScreenViewModel: AnalyticsPromptScreenViewModelType, Analyt var callback: (@MainActor (AnalyticsPromptScreenViewModelAction) -> Void)? /// Initialize a view model with the specified prompt type and app display name. - init() { - let promptStrings = AnalyticsPromptScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL) + init(termsURL: URL) { + let promptStrings = AnalyticsPromptScreenStrings(termsURL: termsURL) super.init(initialViewState: AnalyticsPromptScreenViewState(strings: promptStrings)) } diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift b/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift index e856e0581..a3ca9fff4 100644 --- a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift +++ b/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift @@ -93,7 +93,7 @@ struct AnalyticsPromptScreen: View { // MARK: - Previews struct AnalyticsPromptScreen_Previews: PreviewProvider { - static let viewModel = AnalyticsPromptScreenViewModel() + static let viewModel = AnalyticsPromptScreenViewModel(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL) static var previews: some View { AnalyticsPromptScreen(context: viewModel.context) } diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift index 8ddf72d13..8711ec486 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift @@ -26,7 +26,9 @@ protocol AuthenticationCoordinatorDelegate: AnyObject { class AuthenticationCoordinator: CoordinatorProtocol { private let authenticationService: AuthenticationServiceProxyProtocol private let navigationStackCoordinator: NavigationStackCoordinator - private let serviceLocator: ServiceLocator + private let appSettings: AppSettings + private let analytics: AnalyticsService + private let userIndicatorController: UserIndicatorControllerProtocol private var cancellables: Set = [] @@ -34,10 +36,14 @@ class AuthenticationCoordinator: CoordinatorProtocol { init(authenticationService: AuthenticationServiceProxyProtocol, navigationStackCoordinator: NavigationStackCoordinator, - serviceLocator: ServiceLocator = .shared) { + appSettings: AppSettings, + analytics: AnalyticsService, + userIndicatorController: UserIndicatorControllerProtocol) { self.authenticationService = authenticationService self.navigationStackCoordinator = navigationStackCoordinator - self.serviceLocator = serviceLocator + self.appSettings = appSettings + self.analytics = analytics + self.userIndicatorController = userIndicatorController } func start() { @@ -67,7 +73,7 @@ class AuthenticationCoordinator: CoordinatorProtocol { private func startAuthentication() async { startLoading() - switch await authenticationService.configure(for: serviceLocator.settings.defaultHomeserverAddress) { + switch await authenticationService.configure(for: appSettings.defaultHomeserverAddress) { case .success: stopLoading() showServerConfirmationScreen() @@ -79,7 +85,7 @@ class AuthenticationCoordinator: CoordinatorProtocol { private func showServerSelectionScreen(isModallyPresented: Bool) { let navigationCoordinator = NavigationStackCoordinator() - let userIndicatorController: UserIndicatorControllerProtocol! = isModallyPresented ? UserIndicatorController(rootCoordinator: navigationCoordinator) : serviceLocator.userIndicatorController + let userIndicatorController: UserIndicatorControllerProtocol! = isModallyPresented ? UserIndicatorController(rootCoordinator: navigationCoordinator) : userIndicatorController let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService, userIndicatorController: userIndicatorController, @@ -144,7 +150,9 @@ class AuthenticationCoordinator: CoordinatorProtocol { case .success(let oidcData): stopLoading() - let presenter = OIDCAuthenticationPresenter(authenticationService: authenticationService, presentationAnchor: presentationAnchor) + let presenter = OIDCAuthenticationPresenter(authenticationService: authenticationService, + oidcRedirectURL: appSettings.oidcRedirectURL, + presentationAnchor: presentationAnchor) switch await presenter.authenticate(using: oidcData) { case .success(let userSession): userHasSignedIn(userSession: userSession) @@ -156,7 +164,8 @@ class AuthenticationCoordinator: CoordinatorProtocol { } private func showLoginScreen() { - let parameters = LoginScreenCoordinatorParameters(authenticationService: authenticationService) + let parameters = LoginScreenCoordinatorParameters(authenticationService: authenticationService, + userIndicatorController: userIndicatorController) let coordinator = LoginScreenCoordinator(parameters: parameters) coordinator.callback = { [weak self] action in @@ -203,11 +212,11 @@ class AuthenticationCoordinator: CoordinatorProtocol { } private func showAnalyticsPromptIfNeeded(completion: @escaping () -> Void) { - guard serviceLocator.analytics.shouldShowAnalyticsPrompt else { + guard analytics.shouldShowAnalyticsPrompt else { completion() return } - let coordinator = AnalyticsPromptScreenCoordinator() + let coordinator = AnalyticsPromptScreenCoordinator(analytics: analytics, termsURL: appSettings.analyticsConfiguration.termsURL) coordinator.callback = { completion() } @@ -217,14 +226,14 @@ class AuthenticationCoordinator: CoordinatorProtocol { private static let loadingIndicatorIdentifier = "AuthenticationCoordinatorLoading" private func startLoading() { - serviceLocator.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, - type: .modal, - title: L10n.commonLoading, - persistent: true)) + userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) } private func stopLoading() { - serviceLocator.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) } /// Processes an error to either update the flow or display it to the user. @@ -234,14 +243,14 @@ class AuthenticationCoordinator: CoordinatorProtocol { switch error { case .oidcError(.notSupported): // Temporary alert hijacking the use of .notSupported, can be removed when OIDC support is in the SDK. - serviceLocator.userIndicatorController.alertInfo = AlertInfo(id: UUID(), - title: L10n.commonError, - message: L10n.commonServerNotSupported) + userIndicatorController.alertInfo = AlertInfo(id: UUID(), + title: L10n.commonError, + message: L10n.commonServerNotSupported) case .oidcError(.userCancellation): // No need to show an error, the user cancelled authentication. break default: - serviceLocator.userIndicatorController.alertInfo = AlertInfo(id: UUID()) + userIndicatorController.alertInfo = AlertInfo(id: UUID()) } } } diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift index cf2e93fb4..4e83c67bd 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift @@ -20,6 +20,8 @@ import SwiftUI struct LoginScreenCoordinatorParameters { /// The service used to authenticate the user. let authenticationService: AuthenticationServiceProxyProtocol + + let userIndicatorController: UserIndicatorControllerProtocol } enum LoginScreenCoordinatorAction { @@ -46,7 +48,8 @@ final class LoginScreenCoordinator: CoordinatorProtocol { init(parameters: LoginScreenCoordinatorParameters) { self.parameters = parameters - viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver.value) + viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver.value, + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL) } // MARK: - Public @@ -80,10 +83,10 @@ final class LoginScreenCoordinator: CoordinatorProtocol { private func startLoading(isInteractionBlocking: Bool) { if isInteractionBlocking { - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, - type: .modal, - title: L10n.commonLoading, - persistent: true)) + parameters.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) } else { viewModel.update(isLoading: true) } @@ -91,7 +94,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol { private func stopLoading() { viewModel.update(isLoading: false) - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + parameters.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) } /// Processes an error to either update the flow or display it to the user. diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift index e3b5296b8..e29dba42f 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift @@ -19,9 +19,12 @@ import SwiftUI typealias LoginScreenViewModelType = StateStoreViewModel class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtocol { + private let slidingSyncLearnMoreURL: URL + var callback: (@MainActor (LoginScreenViewModelAction) -> Void)? - init(homeserver: LoginHomeserver) { + init(homeserver: LoginHomeserver, slidingSyncLearnMoreURL: URL) { + self.slidingSyncLearnMoreURL = slidingSyncLearnMoreURL let bindings = LoginScreenBindings() let viewState = LoginScreenViewState(homeserver: homeserver, bindings: bindings) @@ -59,7 +62,7 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc title: L10n.commonError, message: L10n.screenLoginErrorInvalidUserId) case .slidingSyncAlert: - let openURL = { UIApplication.shared.open(ServiceLocator.shared.settings.slidingSyncLearnMoreURL) } + let openURL = { UIApplication.shared.open(self.slidingSyncLearnMoreURL) } state.bindings.alertInfo = AlertInfo(id: .slidingSyncAlert, title: L10n.commonServerNotSupported, message: L10n.screenChangeServerErrorNoSlidingSyncMessage, diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift index 904798a38..d4fc14768 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift @@ -136,20 +136,20 @@ struct LoginScreen: View { struct LoginScreen_Previews: PreviewProvider { static let credentialsViewModel: LoginScreenViewModel = { - let viewModel = LoginScreenViewModel(homeserver: .mockMatrixDotOrg) + let viewModel = LoginScreenViewModel(homeserver: .mockMatrixDotOrg, slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL) viewModel.context.username = "alice" viewModel.context.password = "password" return viewModel }() static var previews: some View { - screen(for: LoginScreenViewModel(homeserver: .mockMatrixDotOrg)) + screen(for: LoginScreenViewModel(homeserver: .mockMatrixDotOrg, slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)) .previewDisplayName("matrix.org") screen(for: credentialsViewModel) .previewDisplayName("Credentials Entered") - screen(for: LoginScreenViewModel(homeserver: .mockUnsupported)) + screen(for: LoginScreenViewModel(homeserver: .mockMatrixDotOrg, slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)) .previewDisplayName("Unsupported") - screen(for: LoginScreenViewModel(homeserver: .mockOIDC)) + screen(for: LoginScreenViewModel(homeserver: .mockMatrixDotOrg, slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)) .previewDisplayName("OIDC Fallback") } diff --git a/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift b/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift index 811aba1e0..7c8da41ab 100644 --- a/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift +++ b/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift @@ -20,10 +20,12 @@ import AuthenticationServices @MainActor class OIDCAuthenticationPresenter: NSObject { private let authenticationService: AuthenticationServiceProxyProtocol + private let oidcRedirectURL: URL private let presentationAnchor: UIWindow - init(authenticationService: AuthenticationServiceProxyProtocol, presentationAnchor: UIWindow) { + init(authenticationService: AuthenticationServiceProxyProtocol, oidcRedirectURL: URL, presentationAnchor: UIWindow) { self.authenticationService = authenticationService + self.oidcRedirectURL = oidcRedirectURL self.presentationAnchor = presentationAnchor super.init() } @@ -32,7 +34,7 @@ class OIDCAuthenticationPresenter: NSObject { func authenticate(using oidcData: OIDCAuthenticationDataProxy) async -> Result { await withCheckedContinuation { continuation in let session = ASWebAuthenticationSession(url: oidcData.url, - callbackURLScheme: ServiceLocator.shared.settings.oidcRedirectURL.scheme) { [weak self] url, error in + callbackURLScheme: oidcRedirectURL.scheme) { [weak self] url, error in guard let self else { return } guard let url else { diff --git a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/MockServerSelectionScreenState.swift b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/MockServerSelectionScreenState.swift index e43a8a25e..15758f2fa 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/MockServerSelectionScreenState.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/MockServerSelectionScreenState.swift @@ -27,17 +27,22 @@ enum MockServerSelectionScreenState: CaseIterable { switch self { case .matrix: return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org", + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL, isModallyPresented: true) + case .emptyAddress: return ServerSelectionScreenViewModel(homeserverAddress: "", + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL, isModallyPresented: true) case .invalidAddress: let viewModel = ServerSelectionScreenViewModel(homeserverAddress: "thisisbad", + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL, isModallyPresented: true) viewModel.displayError(.footerMessage(L10n.errorUnknown)) return viewModel case .nonModal: return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org", + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL, isModallyPresented: false) } } diff --git a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenCoordinator.swift b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenCoordinator.swift index 6365fabaf..0ddeb9adf 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenCoordinator.swift @@ -40,6 +40,7 @@ final class ServerSelectionScreenCoordinator: CoordinatorProtocol { init(parameters: ServerSelectionScreenCoordinatorParameters) { self.parameters = parameters viewModel = ServerSelectionScreenViewModel(homeserverAddress: parameters.authenticationService.homeserver.value.address, + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL, isModallyPresented: parameters.isModallyPresented) userIndicatorController = parameters.userIndicatorController } diff --git a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenModels.swift b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenModels.swift index 53ccb6ac5..02be90fd4 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenModels.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenModels.swift @@ -25,14 +25,7 @@ enum ServerSelectionScreenViewModelAction { struct ServerSelectionScreenViewState: BindableState { /// The message to be shown in the text field footer when no error has occurred. - private let regularFooterMessage = { - let linkPlaceholder = "{link}" - var message = AttributedString(L10n.screenChangeServerFormNotice(linkPlaceholder)) - message.replace(linkPlaceholder, - with: L10n.actionLearnMore, - asLinkTo: ServiceLocator.shared.settings.slidingSyncLearnMoreURL) - return message - }() + private let regularFooterMessage: AttributedString /// View state that can be bound to from SwiftUI. var bindings: ServerSelectionScreenBindings @@ -60,6 +53,19 @@ struct ServerSelectionScreenViewState: BindableState { var hasValidationError: Bool { bindings.homeserverAddress.isEmpty || isShowingFooterError } + + init(slidingSyncLearnMoreURL: URL, bindings: ServerSelectionScreenBindings, footerErrorMessage: String? = nil, isModallyPresented: Bool) { + self.bindings = bindings + self.footerErrorMessage = footerErrorMessage + self.isModallyPresented = isModallyPresented + + let linkPlaceholder = "{link}" + var message = AttributedString(L10n.screenChangeServerFormNotice(linkPlaceholder)) + message.replace(linkPlaceholder, + with: L10n.actionLearnMore, + asLinkTo: slidingSyncLearnMoreURL) + regularFooterMessage = message + } } struct ServerSelectionScreenBindings { diff --git a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift index 52866713e..1936998f0 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift @@ -19,12 +19,16 @@ import SwiftUI typealias ServerSelectionScreenViewModelType = StateStoreViewModel class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, ServerSelectionScreenViewModelProtocol { + private let slidingSyncLearnMoreURL: URL + var callback: (@MainActor (ServerSelectionScreenViewModelAction) -> Void)? - init(homeserverAddress: String, isModallyPresented: Bool) { + init(homeserverAddress: String, slidingSyncLearnMoreURL: URL, isModallyPresented: Bool) { + self.slidingSyncLearnMoreURL = slidingSyncLearnMoreURL let bindings = ServerSelectionScreenBindings(homeserverAddress: homeserverAddress) - super.init(initialViewState: ServerSelectionScreenViewState(bindings: bindings, + super.init(initialViewState: ServerSelectionScreenViewState(slidingSyncLearnMoreURL: slidingSyncLearnMoreURL, + bindings: bindings, isModallyPresented: isModallyPresented)) } @@ -46,7 +50,7 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server state.footerErrorMessage = message } case .slidingSyncAlert: - let openURL = { UIApplication.shared.open(ServiceLocator.shared.settings.slidingSyncLearnMoreURL) } + let openURL = { UIApplication.shared.open(self.slidingSyncLearnMoreURL) } state.bindings.alertInfo = AlertInfo(id: .slidingSyncAlert, title: L10n.commonServerNotSupported, message: L10n.screenChangeServerErrorNoSlidingSyncMessage, diff --git a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift index 238bfc0d2..e5040e927 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift @@ -20,6 +20,7 @@ struct SoftLogoutScreenCoordinatorParameters { let authenticationService: AuthenticationServiceProxyProtocol let credentials: SoftLogoutScreenCredentials let keyBackupNeeded: Bool + let userIndicatorController: UserIndicatorControllerProtocol } enum SoftLogoutScreenCoordinatorResult: CustomStringConvertible { @@ -90,15 +91,15 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol { /// Show an activity indicator whilst loading. @MainActor private func startLoading() { - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, - type: .modal, - title: L10n.commonLoading, - persistent: true)) + parameters.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) } /// Hide the currently displayed activity indicator. @MainActor private func stopLoading() { - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + parameters.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) } /// Shows the forgot password screen. @@ -140,7 +141,9 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol { case .success(let oidcData): stopLoading() - let presenter = OIDCAuthenticationPresenter(authenticationService: parameters.authenticationService, presentationAnchor: presentationAnchor) + let presenter = OIDCAuthenticationPresenter(authenticationService: parameters.authenticationService, + oidcRedirectURL: ServiceLocator.shared.settings.oidcRedirectURL, + presentationAnchor: presentationAnchor) switch await presenter.authenticate(using: oidcData) { case .success(let userSession): callback?(.signedIn(userSession)) diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift index cad3c5b03..b2a10e443 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift @@ -45,9 +45,10 @@ final class CreateRoomCoordinator: CoordinatorProtocol { init(parameters: CreateRoomCoordinatorParameters) { self.parameters = parameters viewModel = CreateRoomViewModel(userSession: parameters.userSession, - userIndicatorController: parameters.userIndicatorController, createRoomParameters: parameters.createRoomParameters, - selectedUsers: parameters.selectedUsers) + selectedUsers: parameters.selectedUsers, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: parameters.userIndicatorController) } func start() { diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index f94ad5863..425d74d8a 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -21,22 +21,28 @@ typealias CreateRoomViewModelType = StateStoreViewModel = .init() private var createRoomParameters: CreateRoomFlowParameters + private let analytics: AnalyticsService private weak var userIndicatorController: UserIndicatorControllerProtocol? + private var actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } init(userSession: UserSessionProtocol, - userIndicatorController: UserIndicatorControllerProtocol?, createRoomParameters: CurrentValuePublisher, - selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>) { + selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>, + analytics: AnalyticsService, + userIndicatorController: UserIndicatorControllerProtocol?) { let parameters = createRoomParameters.value + self.userSession = userSession - self.userIndicatorController = userIndicatorController self.createRoomParameters = parameters + self.analytics = analytics + self.userIndicatorController = userIndicatorController + let bindings = CreateRoomViewStateBindings(roomName: parameters.name, roomTopic: parameters.topic, isRoomPrivate: parameters.isRoomPrivate) super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers.value, bindings: bindings), imageProvider: userSession.mediaProvider) @@ -156,7 +162,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol userIDs: state.selectedUsers.map(\.userID), avatarURL: avatarURL) { case .success(let roomId): - ServiceLocator.shared.analytics.trackCreatedRoom(isDM: false) + analytics.trackCreatedRoom(isDM: false) actionsSubject.send(.openRoom(withIdentifier: roomId)) case .failure(let failure): displayError(failure) diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift index 242418be6..6ddc1e0d9 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift @@ -211,14 +211,22 @@ struct CreateRoom_Previews: PreviewProvider { let parameters = CreateRoomFlowParameters() let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie] - return CreateRoomViewModel(userSession: userSession, userIndicatorController: nil, createRoomParameters: .init(parameters), selectedUsers: .init(selectedUsers)) + return CreateRoomViewModel(userSession: userSession, + createRoomParameters: .init(parameters), + selectedUsers: .init(selectedUsers), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: nil) }() static let emtpyViewModel = { let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), mediaProvider: MockMediaProvider()) let parameters = CreateRoomFlowParameters() - return CreateRoomViewModel(userSession: userSession, userIndicatorController: nil, createRoomParameters: .init(parameters), selectedUsers: .init([])) + return CreateRoomViewModel(userSession: userSession, + createRoomParameters: .init(parameters), + selectedUsers: .init([]), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: nil) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index fa6c8b637..ab23aa9d3 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -50,7 +50,9 @@ final class HomeScreenCoordinator: CoordinatorProtocol { viewModel = HomeScreenViewModel(userSession: parameters.userSession, attributedStringBuilder: parameters.attributedStringBuilder, - selectedRoomPublisher: parameters.selectedRoomPublisher) + selectedRoomPublisher: parameters.selectedRoomPublisher, + appSettings: ServiceLocator.shared.settings, + userIndicatorController: ServiceLocator.shared.userIndicatorController) viewModel.callback = { [weak self] action in guard let self else { return } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index a61e5578c..6db9aef4a 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -21,9 +21,12 @@ typealias HomeScreenViewModelType = StateStoreViewModel, isScrolling: Bool), Never>((0..<0, false)) @@ -33,9 +36,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // swiftlint:disable:next function_body_length cyclomatic_complexity init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol, - selectedRoomPublisher: CurrentValuePublisher) { + selectedRoomPublisher: CurrentValuePublisher, + appSettings: AppSettings, + userIndicatorController: UserIndicatorControllerProtocol) { self.userSession = userSession self.attributedStringBuilder = attributedStringBuilder + self.appSettings = appSettings + self.userIndicatorController = userIndicatorController roomSummaryProvider = userSession.clientProxy.roomSummaryProvider inviteSummaryProvider = userSession.clientProxy.inviteSummaryProvider @@ -123,7 +130,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .store(in: &cancellables) inviteSummaryProvider.roomListPublisher - .combineLatest(ServiceLocator.shared.settings.$seenInvites) + .combineLatest(appSettings.$seenInvites) .receive(on: DispatchQueue.main) .sink { [weak self] summaries, readInvites in self?.state.hasPendingInvitations = !summaries.isEmpty @@ -264,9 +271,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private func startLeaveRoomProcess(roomId: String) { Task { defer { - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID) + userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID) } - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLoading, persistent: true)) + userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLoading, persistent: true)) let room = await userSession.clientProxy.roomForIdentifier(roomId) @@ -286,9 +293,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private func leaveRoom(roomId: String) { Task { defer { - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID) + userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID) } - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLeavingRoom, persistent: true)) + userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLeavingRoom, persistent: true)) let room = await userSession.clientProxy.roomForIdentifier(roomId) let result = await room?.leaveRoom() @@ -297,10 +304,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol case .none, .some(.failure): state.bindings.alertInfo = AlertInfo(id: UUID(), title: L10n.errorUnknown) case .some(.success): - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: UUID().uuidString, - type: .modal(progress: .none, interactiveDismissDisabled: false), - title: L10n.commonCurrentUserLeftRoom, - iconName: "checkmark")) + userIndicatorController.submitIndicator(UserIndicator(id: UUID().uuidString, + type: .modal(progress: .none, interactiveDismissDisabled: false), + title: L10n.commonCurrentUserLeftRoom, + iconName: "checkmark")) callback?(.roomLeft(roomIdentifier: roomId)) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index dd534a293..5da758002 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -215,8 +215,10 @@ struct HomeScreen_Previews: PreviewProvider { mediaProvider: MockMediaProvider()) let viewModel = HomeScreenViewModel(userSession: userSession, - attributedStringBuilder: AttributedStringBuilder(), - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher()) + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL), + selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + userIndicatorController: ServiceLocator.shared.userIndicatorController) return NavigationStack { HomeScreen(context: viewModel.context) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 03d66268e..7d1fb1b4a 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -174,8 +174,10 @@ struct HomeScreenRoomCell_Previews: PreviewProvider { mediaProvider: MockMediaProvider()) let viewModel = HomeScreenViewModel(userSession: userSession, - attributedStringBuilder: AttributedStringBuilder(), - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher()) + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL), + selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + userIndicatorController: ServiceLocator.shared.userIndicatorController) let rooms: [HomeScreenRoom] = summaryProvider.roomListPublisher.value.compactMap { summary -> HomeScreenRoom? in switch summary { diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift index 12c6c5c83..8ae2967db 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift @@ -49,6 +49,7 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { roomType: parameters.roomType, mediaProvider: parameters.mediaProvider, userDiscoveryService: parameters.userDiscoveryService, + appSettings: ServiceLocator.shared.settings, userIndicatorController: parameters.userIndicatorController) } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 054566805..e30bba27c 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -21,10 +21,12 @@ import SwiftUI typealias InviteUsersScreenViewModelType = StateStoreViewModel class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol { + private let roomType: InviteUsersScreenRoomType private let mediaProvider: MediaProviderProtocol private let userDiscoveryService: UserDiscoveryServiceProtocol - private let roomType: InviteUsersScreenRoomType + private let appSettings: AppSettings private weak var userIndicatorController: UserIndicatorControllerProtocol? + private let actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { @@ -35,10 +37,12 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr roomType: InviteUsersScreenRoomType, mediaProvider: MediaProviderProtocol, userDiscoveryService: UserDiscoveryServiceProtocol, + appSettings: AppSettings, userIndicatorController: UserIndicatorControllerProtocol?) { self.roomType = roomType self.mediaProvider = mediaProvider self.userDiscoveryService = userDiscoveryService + self.appSettings = appSettings self.userIndicatorController = userIndicatorController super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers.value, isCreatingRoom: roomType.isCreatingRoom), imageProvider: mediaProvider) @@ -137,7 +141,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr } private func fetchSuggestions() { - guard ServiceLocator.shared.settings.userSuggestionsEnabled else { + guard appSettings.userSuggestionsEnabled else { state.usersSection = .init(type: .suggestions, users: []) return } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift index 28bbbabd8..6988b8d12 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift @@ -148,7 +148,12 @@ struct InviteUsersScreen_Previews: PreviewProvider { let userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) - return InviteUsersScreenViewModel(selectedUsers: .init([]), roomType: .draft, mediaProvider: MockMediaProvider(), userDiscoveryService: userDiscoveryService, userIndicatorController: UserIndicatorControllerMock()) + return InviteUsersScreenViewModel(selectedUsers: .init([]), + roomType: .draft, + mediaProvider: MockMediaProvider(), + userDiscoveryService: userDiscoveryService, + appSettings: ServiceLocator.shared.settings, + userIndicatorController: UserIndicatorControllerMock()) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenCoordinator.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenCoordinator.swift index 38b8ac87e..411e93f96 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenCoordinator.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenCoordinator.swift @@ -37,7 +37,10 @@ final class InvitesScreenCoordinator: CoordinatorProtocol { init(parameters: InvitesScreenCoordinatorParameters) { self.parameters = parameters - viewModel = InvitesScreenViewModel(userSession: parameters.userSession) + viewModel = InvitesScreenViewModel(userSession: parameters.userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) } func start() { diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift index e608f796f..eb63c34b5 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift @@ -20,17 +20,28 @@ import SwiftUI typealias InvitesScreenViewModelType = StateStoreViewModel class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModelProtocol { - private var actionsSubject: PassthroughSubject = .init() private let userSession: UserSessionProtocol + private let appSettings: AppSettings + private let analytics: AnalyticsService + private let userIndicatorController: UserIndicatorControllerProtocol + private let previouslySeenInvites: Set + private let actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } - init(userSession: UserSessionProtocol) { + init(userSession: UserSessionProtocol, + appSettings: AppSettings, + analytics: AnalyticsService, + userIndicatorController: UserIndicatorControllerProtocol) { self.userSession = userSession - previouslySeenInvites = ServiceLocator.shared.settings.seenInvites + self.appSettings = appSettings + self.analytics = analytics + self.userIndicatorController = userIndicatorController + + previouslySeenInvites = appSettings.seenInvites super.init(initialViewState: InvitesScreenViewState(), imageProvider: userSession.mediaProvider) setupSubscriptions() } @@ -68,7 +79,7 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel guard let self else { return } let invites = self.buildInvites(from: roomSummaries) - ServiceLocator.shared.settings.seenInvites = Set(invites.map(\.roomDetails.id)) + appSettings.seenInvites = Set(invites.map(\.roomDetails.id)) self.state.invites = invites for invite in invites { @@ -119,10 +130,10 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel Task { let roomID = invite.roomDetails.id defer { - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(roomID) + userIndicatorController.retractIndicatorWithId(roomID) } - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true)) + userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true)) guard let roomProxy = await clientProxy.roomForIdentifier(roomID) else { displayError(.failedAcceptingInvite) @@ -132,6 +143,7 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel switch await roomProxy.acceptInvitation() { case .success: actionsSubject.send(.openRoom(withIdentifier: roomID)) + analytics.trackJoinedRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace, activeMemberCount: UInt(roomProxy.activeMembersCount)) case .failure(let error): displayError(error) } @@ -142,10 +154,10 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel Task { let roomID = invite.roomDetails.id defer { - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(roomID) + userIndicatorController.retractIndicatorWithId(roomID) } - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true)) + userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true)) guard let roomProxy = await clientProxy.roomForIdentifier(roomID) else { displayError(.failedRejectingInvite) diff --git a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift index 179d2c816..05c9672bc 100644 --- a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift +++ b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift @@ -73,7 +73,10 @@ private extension InvitesScreenViewModel { static let noInvites: InvitesScreenViewModel = { let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), mediaProvider: MockMediaProvider()) - let regularViewModel = InvitesScreenViewModel(userSession: userSession) + let regularViewModel = InvitesScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) return regularViewModel }() @@ -83,7 +86,10 @@ private extension InvitesScreenViewModel { clientProxy.roomSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) - let regularViewModel = InvitesScreenViewModel(userSession: userSession) + let regularViewModel = InvitesScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) return regularViewModel }() } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift index c49fc981b..21224d0c9 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift @@ -23,6 +23,7 @@ struct RoomDetailsScreenCoordinatorParameters { let roomProxy: RoomProxyProtocol let mediaProvider: MediaProviderProtocol let userDiscoveryService: UserDiscoveryServiceProtocol + let userIndicatorController: UserIndicatorControllerProtocol } enum RoomDetailsScreenCoordinatorAction { @@ -45,7 +46,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { viewModel = RoomDetailsScreenViewModel(accountUserID: parameters.accountUserID, roomProxy: parameters.roomProxy, - mediaProvider: parameters.mediaProvider) + mediaProvider: parameters.mediaProvider, + userIndicatorController: parameters.userIndicatorController) } // MARK: - Public @@ -175,9 +177,9 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { return } - ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.commonUnableToInviteTitle, - message: L10n.commonUnableToInviteMessage) + parameters.userIndicatorController.alertInfo = .init(id: .init(), + title: L10n.commonUnableToInviteTitle, + message: L10n.commonUnableToInviteMessage) } } } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index bbb0d7566..ce9cc0c27 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -22,6 +22,7 @@ typealias RoomDetailsScreenViewModelType = StateStoreViewModel Void)? - init(roomProxy: RoomProxyProtocol, mediaProvider: MediaProviderProtocol) { + init(roomProxy: RoomProxyProtocol, + mediaProvider: MediaProviderProtocol, + userIndicatorController: UserIndicatorControllerProtocol) { self.roomProxy = roomProxy + self.userIndicatorController = userIndicatorController + super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount), imageProvider: mediaProvider) @@ -108,11 +114,11 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe private let userIndicatorID = UUID().uuidString private func showLoader() { - ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) + userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) } private func hideLoader() { - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(userIndicatorID) + userIndicatorController.retractIndicatorWithId(userIndicatorID) } } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift index dea7f93a7..1b5c3cfc7 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreen.swift @@ -82,7 +82,8 @@ struct RoomMembersListScreen_Previews: PreviewProvider { .mockCharlie ] return RoomMembersListScreenViewModel(roomProxy: RoomProxyMock(with: .init(displayName: "Some room", members: members)), - mediaProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift index f6bdcbd99..4accb5c0f 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift @@ -54,7 +54,8 @@ struct RoomMembersListMemberCell_Previews: PreviewProvider { .mockCharlie ] let viewModel = RoomMembersListScreenViewModel(roomProxy: RoomProxyMock(with: .init(displayName: "Some room", members: members)), - mediaProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) return VStack { ForEach(members, id: \.userID) { member in diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index 125bcaf1f..e494e775d 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -50,7 +50,10 @@ final class RoomScreenCoordinator: CoordinatorProtocol { viewModel = RoomScreenViewModel(timelineController: parameters.timelineController, mediaProvider: parameters.mediaProvider, - roomProxy: parameters.roomProxy) + roomProxy: parameters.roomProxy, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) } // MARK: - Public diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index d119c9fc7..a67d6909e 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -27,23 +27,29 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol static let toastErrorID = "RoomScreenToastError" } - private let roomProxy: RoomProxyProtocol private let timelineController: RoomTimelineControllerProtocol + private let roomProxy: RoomProxyProtocol + private let appSettings: AppSettings + private let analytics: AnalyticsService private unowned let userIndicatorController: UserIndicatorControllerProtocol init(timelineController: RoomTimelineControllerProtocol, mediaProvider: MediaProviderProtocol, roomProxy: RoomProxyProtocol, - userIndicatorController: UserIndicatorControllerProtocol = ServiceLocator.shared.userIndicatorController) { + appSettings: AppSettings, + analytics: AnalyticsService, + userIndicatorController: UserIndicatorControllerProtocol) { self.roomProxy = roomProxy self.timelineController = timelineController + self.appSettings = appSettings + self.analytics = analytics self.userIndicatorController = userIndicatorController super.init(initialViewState: RoomScreenViewState(roomId: timelineController.roomID, roomTitle: roomProxy.roomTitle, roomAvatarURL: roomProxy.avatarURL, - timelineStyle: ServiceLocator.shared.settings.timelineStyle, - readReceiptsEnabled: ServiceLocator.shared.settings.readReceiptsEnabled, + timelineStyle: appSettings.timelineStyle, + readReceiptsEnabled: appSettings.readReceiptsEnabled, isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom, bindings: .init(composerText: "", composerFocused: false)), imageProvider: mediaProvider) @@ -150,11 +156,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } .store(in: &cancellables) - ServiceLocator.shared.settings.$timelineStyle + appSettings.$timelineStyle .weakAssign(to: \.state.timelineStyle, on: self) .store(in: &cancellables) - ServiceLocator.shared.settings.$readReceiptsEnabled + appSettings.$readReceiptsEnabled .weakAssign(to: \.state.readReceiptsEnabled, on: self) .store(in: &cancellables) @@ -310,7 +316,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol break } - ServiceLocator.shared.analytics.trackComposer(inThread: false, isEditing: isEdit, isReply: isReply, startsThread: nil) + analytics.trackComposer(inThread: false, isEditing: isEdit, isReply: isReply, startsThread: nil) } private func displayError(_ type: RoomScreenErrorType) { @@ -375,7 +381,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol actions.append(.report) } - var debugActions: [TimelineItemMenuAction] = ServiceLocator.shared.settings.canShowDeveloperOptions ? [.viewSource] : [] + var debugActions: [TimelineItemMenuAction] = appSettings.canShowDeveloperOptions ? [.viewSource] : [] if let item = timelineItem as? EncryptedRoomTimelineItem, case let .megolmV1AesSha2(sessionID) = item.encryptionType { @@ -393,7 +399,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol return .init(actions: actions, debugActions: debugActions) } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length private func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: String) { guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }), let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else { @@ -417,7 +423,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol setComposerMode(.edit(originalItemId: messageTimelineItem.id)) case .copyPermalink: do { - let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventTimelineItem.id, roomIdentifier: timelineController.roomID) + let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventTimelineItem.id, roomIdentifier: timelineController.roomID, + baseURL: appSettings.permalinkBaseURL) UIPasteboard.general.url = permalink } catch { displayError(.alert(L10n.errorFailedCreatingThePermalink)) @@ -621,5 +628,8 @@ private extension RoomProxyProtocol { extension RoomScreenViewModel { static let mock = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: "Preview room"))) + roomProxy: RoomProxyMock(with: .init(displayName: "Preview room")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift index a45efab28..7a1982e58 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift @@ -88,7 +88,10 @@ struct RoomAttachmentPicker: View { struct RoomAttachmentPicker_Previews: PreviewProvider { static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: ""))) + roomProxy: RoomProxyMock(with: .init(displayName: "")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) static var previews: some View { RoomAttachmentPicker(context: viewModel.context) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift index 90ac897db..0bfb09f29 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift @@ -59,7 +59,10 @@ struct RoomHeaderView_Previews: PreviewProvider { static var bodyPlain: some View { let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: "Some Room name", avatarURL: URL.picturesDirectory))) + roomProxy: RoomProxyMock(with: .init(displayName: "Some Room name", avatarURL: URL.picturesDirectory)), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) RoomHeaderView(context: viewModel.context) .previewLayout(.sizeThatFits) @@ -70,7 +73,10 @@ struct RoomHeaderView_Previews: PreviewProvider { static var bodyEncrypted: some View { let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: "Some Room name"))) + roomProxy: RoomProxyMock(with: .init(displayName: "Some Room name")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) RoomHeaderView(context: viewModel.context) .previewLayout(.sizeThatFits) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 306837cf0..52d0e539d 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -157,7 +157,10 @@ struct RoomScreen: View { struct RoomScreen_Previews: PreviewProvider { static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: "Preview room"))) + roomProxy: RoomProxyMock(with: .init(displayName: "Preview room")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift index 5c69705ac..f2cd3e94c 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReadReceiptsView.swift @@ -61,7 +61,10 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider { static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), roomProxy: RoomProxyMock(with: .init(displayName: "Test", - members: members))) + members: members)), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) static let singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")] static let doubleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"), diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index e38251d3f..591dc41bd 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -167,7 +167,7 @@ struct FormattedBodyText_Previews: PreviewProvider { """ ] - let attributedStringBuilder = AttributedStringBuilder() + let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL) VStack(alignment: .leading, spacing: 24.0) { ForEach(htmlStrings, id: \.self) { htmlString in diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift index 9b1d5bd8d..bec515e08 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift @@ -84,7 +84,10 @@ struct TimelineView: UIViewControllerRepresentable { struct TimelineTableView_Previews: PreviewProvider { static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: "Preview room"))) + roomProxy: RoomProxyMock(with: .init(displayName: "Preview room")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenCoordinator.swift index a27dc0021..b2431823a 100644 --- a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenCoordinator.swift @@ -17,11 +17,17 @@ import Combine import SwiftUI +struct AnalyticsSettingsScreenCoordinatorParameters { + let appSettings: AppSettings + let analytics: AnalyticsService +} + final class AnalyticsSettingsScreenCoordinator: CoordinatorProtocol { private let viewModel: AnalyticsSettingsScreenViewModel - init() { - viewModel = AnalyticsSettingsScreenViewModel() + init(parameters: AnalyticsSettingsScreenCoordinatorParameters) { + viewModel = AnalyticsSettingsScreenViewModel(appSettings: parameters.appSettings, + analytics: parameters.analytics) } func toPresentable() -> AnyView { diff --git a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift index c0c3f056b..961bc794d 100644 --- a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift @@ -20,14 +20,20 @@ import SwiftUI typealias AnalyticsSettingsScreenViewModelType = StateStoreViewModel class AnalyticsSettingsScreenViewModel: AnalyticsSettingsScreenViewModelType, AnalyticsSettingsScreenViewModelProtocol { - init() { - let strings = AnalyticsSettingsScreenStrings(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL) - let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: ServiceLocator.shared.analytics.isEnabled) + private let appSettings: AppSettings + private let analytics: AnalyticsService + + init(appSettings: AppSettings, analytics: AnalyticsService) { + self.appSettings = appSettings + self.analytics = analytics + + let strings = AnalyticsSettingsScreenStrings(termsURL: appSettings.analyticsConfiguration.termsURL) + let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: analytics.isEnabled) let state = AnalyticsSettingsScreenViewState(strings: strings, bindings: bindings) super.init(initialViewState: state) - ServiceLocator.shared.settings.$analyticsConsentState + appSettings.$analyticsConsentState .map { $0 == .optedIn } .weakAssign(to: \.state.bindings.enableAnalytics, on: self) .store(in: &cancellables) @@ -36,10 +42,10 @@ class AnalyticsSettingsScreenViewModel: AnalyticsSettingsScreenViewModelType, An override func process(viewAction: AnalyticsSettingsScreenViewAction) { switch viewAction { case .toggleAnalytics: - if ServiceLocator.shared.analytics.isEnabled { - ServiceLocator.shared.analytics.optOut() + if analytics.isEnabled { + analytics.optOut() } else { - ServiceLocator.shared.analytics.optIn() + analytics.optIn() } } } diff --git a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift index b20c56cab..a2c826059 100644 --- a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift @@ -49,7 +49,11 @@ struct AnalyticsSettingsScreen: View { struct AnalyticsSettingsScreen_Previews: PreviewProvider { static var previews: some View { - let viewModel = AnalyticsSettingsScreenViewModel() + let appSettings = AppSettings() + let viewModel = AnalyticsSettingsScreenViewModel(appSettings: appSettings, + analytics: AnalyticsService(client: AnalyticsClientMock(), + appSettings: appSettings, + bugReportService: BugReportServiceMock())) AnalyticsSettingsScreen(context: viewModel.context) } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift index 1b42d3054..38a008194 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift @@ -26,7 +26,7 @@ final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol { var callback: ((DeveloperOptionsScreenCoordinatorAction) -> Void)? init() { - viewModel = DeveloperOptionsScreenViewModel() + viewModel = DeveloperOptionsScreenViewModel(appSettings: ServiceLocator.shared.settings) viewModel.callback = { [weak self] action in switch action { case .clearCache: diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift index 7fcc287f7..8d0fda818 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift @@ -19,12 +19,13 @@ import SwiftUI typealias DeveloperOptionsScreenViewModelType = StateStoreViewModel class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, DeveloperOptionsScreenViewModelProtocol { - var callback: ((DeveloperOptionsScreenViewModelAction) -> Void)? - private let appSettings: AppSettings - init() { - appSettings = ServiceLocator.shared.settings + var callback: ((DeveloperOptionsScreenViewModelAction) -> Void)? + + init(appSettings: AppSettings) { + self.appSettings = appSettings + let bindings = DeveloperOptionsScreenViewStateBindings(shouldCollapseRoomStateEvents: appSettings.shouldCollapseRoomStateEvents, userSuggestionsEnabled: appSettings.userSuggestionsEnabled, readReceiptsEnabled: appSettings.readReceiptsEnabled, diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 9a8da637f..e88174342 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -105,7 +105,7 @@ struct DeveloperOptionsScreen: View { struct DeveloperOptionsScreen_Previews: PreviewProvider { static var previews: some View { - let viewModel = DeveloperOptionsScreenViewModel() + let viewModel = DeveloperOptionsScreenViewModel(appSettings: ServiceLocator.shared.settings) DeveloperOptionsScreen(context: viewModel.context) } } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift index 18654e87d..f63a8964b 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift @@ -40,7 +40,7 @@ final class SettingsScreenCoordinator: CoordinatorProtocol { init(parameters: SettingsScreenCoordinatorParameters) { self.parameters = parameters - viewModel = SettingsScreenViewModel(withUserSession: parameters.userSession) + viewModel = SettingsScreenViewModel(userSession: parameters.userSession, appSettings: ServiceLocator.shared.settings) viewModel.callback = { [weak self] action in guard let self else { return } @@ -72,7 +72,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol { // MARK: - Private private func presentAnalyticsScreen() { - let coordinator = AnalyticsSettingsScreenCoordinator() + let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics)) parameters.navigationStackCoordinator?.push(coordinator) } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index 3e8753fb0..7367d298c 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -21,12 +21,15 @@ typealias SettingsScreenViewModelType = StateStoreViewModel Void)? - init(withUserSession userSession: UserSessionProtocol) { + init(userSession: UserSessionProtocol, appSettings: AppSettings) { self.userSession = userSession - let bindings = SettingsScreenViewStateBindings(timelineStyle: ServiceLocator.shared.settings.timelineStyle) + self.appSettings = appSettings + + let bindings = SettingsScreenViewStateBindings(timelineStyle: appSettings.timelineStyle) var showSessionVerificationSection = false if let sessionVerificationController = userSession.sessionVerificationController { @@ -37,10 +40,10 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo deviceID: userSession.deviceID, userID: userSession.userID, showSessionVerificationSection: showSessionVerificationSection, - showDeveloperOptions: ServiceLocator.shared.settings.canShowDeveloperOptions), + showDeveloperOptions: appSettings.canShowDeveloperOptions), imageProvider: userSession.mediaProvider) - ServiceLocator.shared.settings.$timelineStyle + appSettings.$timelineStyle .weakAssign(to: \.state.bindings.timelineStyle, on: self) .store(in: &cancellables) @@ -88,7 +91,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo case .sessionVerification: callback?(.sessionVerification) case .changedTimelineStyle: - ServiceLocator.shared.settings.timelineStyle = state.bindings.timelineStyle + appSettings.timelineStyle = state.bindings.timelineStyle case .developerOptions: callback?(.developerOptions) } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index 0f85bf00f..4b1b4875c 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -194,7 +194,8 @@ struct SettingsScreen_Previews: PreviewProvider { static let viewModel = { let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), mediaProvider: MockMediaProvider()) - return SettingsScreenViewModel(withUserSession: userSession) + return SettingsScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index 65fa80b29..cb0973fdb 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -60,7 +60,11 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { init(parameters: StartChatScreenCoordinatorParameters) { self.parameters = parameters - viewModel = StartChatScreenViewModel(userSession: parameters.userSession, userIndicatorController: parameters.userIndicatorController, userDiscoveryService: parameters.userDiscoveryService) + viewModel = StartChatScreenViewModel(userSession: parameters.userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: parameters.userIndicatorController, + userDiscoveryService: parameters.userDiscoveryService) } func start() { diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift index 39c741f96..c719e61cf 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift @@ -21,19 +21,28 @@ typealias StartChatScreenViewModelType = StateStoreViewModel = .init() + private let appSettings: AppSettings + private let analytics: AnalyticsService + private weak var userIndicatorController: UserIndicatorControllerProtocol? private let userDiscoveryService: UserDiscoveryServiceProtocol + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } - weak var userIndicatorController: UserIndicatorControllerProtocol? - - init(userSession: UserSessionProtocol, userIndicatorController: UserIndicatorControllerProtocol?, userDiscoveryService: UserDiscoveryServiceProtocol) { + init(userSession: UserSessionProtocol, + appSettings: AppSettings, + analytics: AnalyticsService, + userIndicatorController: UserIndicatorControllerProtocol?, + userDiscoveryService: UserDiscoveryServiceProtocol) { self.userSession = userSession + self.appSettings = appSettings + self.analytics = analytics self.userIndicatorController = userIndicatorController self.userDiscoveryService = userDiscoveryService + super.init(initialViewState: StartChatScreenViewState(userID: userSession.userID), imageProvider: userSession.mediaProvider) setupBindings() @@ -106,7 +115,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie } private func fetchSuggestions() { - guard ServiceLocator.shared.settings.userSuggestionsEnabled else { + guard appSettings.userSuggestionsEnabled else { state.usersSection = .init(type: .suggestions, users: []) return } @@ -133,7 +142,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie showLoadingIndicator() switch await clientProxy.createDirectRoom(with: user.userID, expectedRoomName: user.displayName) { case .success(let roomId): - ServiceLocator.shared.analytics.trackCreatedRoom(isDM: true) + analytics.trackCreatedRoom(isDM: true) actionsSubject.send(.openRoom(withIdentifier: roomId)) case .failure(let failure): displayError(failure) diff --git a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift index 86c34b3ed..4df698aab 100644 --- a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift +++ b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift @@ -140,7 +140,11 @@ struct StartChatScreen_Previews: PreviewProvider { let userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) - let viewModel = StartChatScreenViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService) + let viewModel = StartChatScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: nil, + userDiscoveryService: userDiscoveryService) return viewModel }() diff --git a/ElementX/Sources/Services/Analytics/AnalyticsClientProtocol.swift b/ElementX/Sources/Services/Analytics/AnalyticsClientProtocol.swift index a399e372c..68c644f50 100644 --- a/ElementX/Sources/Services/Analytics/AnalyticsClientProtocol.swift +++ b/ElementX/Sources/Services/Analytics/AnalyticsClientProtocol.swift @@ -22,7 +22,7 @@ protocol AnalyticsClientProtocol { var isRunning: Bool { get } /// Starts the analytics client reporting data. - func start() + func start(analyticsConfiguration: AnalyticsConfiguration) /// Reset all stored properties and any event queues on the client. Note that /// the client will remain active, but in a fresh unidentified state. diff --git a/ElementX/Sources/Services/Analytics/Analytics.swift b/ElementX/Sources/Services/Analytics/AnalyticsService.swift similarity index 87% rename from ElementX/Sources/Services/Analytics/Analytics.swift rename to ElementX/Sources/Services/Analytics/AnalyticsService.swift index b57c080bd..a682665ba 100644 --- a/ElementX/Sources/Services/Analytics/Analytics.swift +++ b/ElementX/Sources/Services/Analytics/AnalyticsService.swift @@ -30,12 +30,16 @@ import PostHog /// To add a new event create a PR to that repo with the new/updated schema. Once merged /// into `main`, update the AnalyticsEvents Swift package in `project.yml`. /// -class Analytics { +class AnalyticsService { /// The analytics client to send events with. private let client: AnalyticsClientProtocol + private let appSettings: AppSettings + private let bugReportService: BugReportServiceProtocol - init(client: AnalyticsClientProtocol) { + init(client: AnalyticsClientProtocol, appSettings: AppSettings, bugReportService: BugReportServiceProtocol) { self.client = client + self.appSettings = appSettings + self.bugReportService = bugReportService } /// Whether or not the object is enabled and sending events to the server. @@ -44,27 +48,27 @@ class Analytics { /// Whether to show the user the analytics opt in prompt. var shouldShowAnalyticsPrompt: Bool { // Only show the prompt once, and when analytics are enabled in BuildSettings. - ServiceLocator.shared.settings.analyticsConsentState == .unknown && ServiceLocator.shared.settings.analyticsConfiguration.isEnabled + appSettings.analyticsConsentState == .unknown && appSettings.analyticsConfiguration.isEnabled } var isEnabled: Bool { - ServiceLocator.shared.settings.analyticsConsentState == .optedIn + appSettings.analyticsConsentState == .optedIn } /// Opts in to analytics tracking with the supplied user session. func optIn() { - ServiceLocator.shared.settings.analyticsConsentState = .optedIn + appSettings.analyticsConsentState = .optedIn startIfEnabled() } /// Stops analytics tracking and calls `reset` to clear any IDs and event queues. func optOut() { - ServiceLocator.shared.settings.analyticsConsentState = .optedOut + appSettings.analyticsConsentState = .optedOut // The order is important here. PostHog ignores the reset if stopped. reset() client.stop() - ServiceLocator.shared.bugReportService.stop() + bugReportService.stop() MXLog.info("Stopped.") } @@ -72,8 +76,8 @@ class Analytics { func startIfEnabled() { guard isEnabled, !isRunning else { return } - client.start() - ServiceLocator.shared.bugReportService.start() + client.start(analyticsConfiguration: appSettings.analyticsConfiguration) + bugReportService.start() // Sanity check in case something went wrong. guard client.isRunning else { return } @@ -87,14 +91,14 @@ class Analytics { /// Note: **MUST** be called before stopping PostHog or the reset is ignored. func reset() { client.reset() - ServiceLocator.shared.bugReportService.reset() + bugReportService.reset() MXLog.info("Reset.") } /// Reset the consent state for analytics func resetConsentState() { MXLog.warning("Resetting consent state for analytics.") - ServiceLocator.shared.settings.analyticsConsentState = .unknown + appSettings.analyticsConsentState = .unknown } /// Flushes the event queue in the analytics client, uploading all pending events. @@ -116,7 +120,7 @@ class Analytics { // MARK: - Public tracking methods -extension Analytics { +extension AnalyticsService { /// Track the presentation of a screen /// - Parameter screen: The screen that was shown /// - Parameter duration: An optional value representing how long the screen was shown for in milliseconds. diff --git a/ElementX/Sources/Services/Analytics/Helpers/Analytics+SwiftUI.swift b/ElementX/Sources/Services/Analytics/Helpers/Analytics+SwiftUI.swift new file mode 100644 index 000000000..4e74ca59d --- /dev/null +++ b/ElementX/Sources/Services/Analytics/Helpers/Analytics+SwiftUI.swift @@ -0,0 +1,28 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +private struct AnalyticsServiceKey: EnvironmentKey { + static let defaultValue: AnalyticsService = ServiceLocator.shared.analytics +} + +extension EnvironmentValues { + var analyticsService: AnalyticsService { + get { self[AnalyticsServiceKey.self] } + set { self[AnalyticsServiceKey.self] = newValue } + } +} diff --git a/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift b/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift index 4da877e6c..76d1d1449 100644 --- a/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift +++ b/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift @@ -17,8 +17,7 @@ import PostHog extension PHGPostHogConfiguration { - static var standard: PHGPostHogConfiguration? { - let analyticsConfiguration = ServiceLocator.shared.settings.analyticsConfiguration + static func standard(analyticsConfiguration: AnalyticsConfiguration) -> PHGPostHogConfiguration? { guard analyticsConfiguration.isEnabled else { return nil } let postHogConfiguration = PHGPostHogConfiguration(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host) diff --git a/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift b/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift index 7f6e3cd56..bf8e0870e 100644 --- a/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift +++ b/ElementX/Sources/Services/Analytics/PostHogAnalyticsClient.swift @@ -27,9 +27,9 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol { var isRunning: Bool { postHog?.enabled ?? false } - func start() { + func start(analyticsConfiguration: AnalyticsConfiguration) { // Only start if analytics have been configured in BuildSettings - guard let configuration = PHGPostHogConfiguration.standard else { return } + guard let configuration = PHGPostHogConfiguration.standard(analyticsConfiguration: analyticsConfiguration) else { return } if postHog == nil { postHog = PHGPostHog(configuration: configuration) diff --git a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift index cd3aaf019..5414a7151 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift @@ -22,26 +22,27 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { private let authenticationService: AuthenticationService private let userSessionStore: UserSessionStoreProtocol - private let homeserverSubject: CurrentValueSubject = .init(LoginHomeserver(address: ServiceLocator.shared.settings.defaultHomeserverAddress, - loginMode: .unknown)) + private let homeserverSubject: CurrentValueSubject var homeserver: CurrentValuePublisher { homeserverSubject.asCurrentValuePublisher() } - init(userSessionStore: UserSessionStoreProtocol) { + init(userSessionStore: UserSessionStoreProtocol, appSettings: AppSettings) { self.userSessionStore = userSessionStore - // guard let settings = ServiceLocator.shared.settings else { fatalError("The settings must be set.") } + homeserverSubject = .init(LoginHomeserver(address: appSettings.defaultHomeserverAddress, + loginMode: .unknown)) + // let oidcConfiguration = OidcConfiguration(clientName: InfoPlistReader.main.bundleDisplayName, // redirectUri: settings.oidcRedirectURL.absoluteString, - // clientUri: settings.oidcClientURL.absoluteString, - // tosUri: settings.oidcTermsURL.absoluteString, - // policyUri: settings.oidcPolicyURL.absoluteString, - // staticRegistrations: settings.oidcStaticRegistrations.mapKeys { $0.absoluteString }) + // clientUri: appSettings.oidcClientURL.absoluteString, + // tosUri: appSettings.oidcTermsURL.absoluteString, + // policyUri: appSettings.oidcPolicyURL.absoluteString, + // staticRegistrations: appSettings.oidcStaticRegistrations.mapKeys { $0.absoluteString }) authenticationService = AuthenticationService(basePath: userSessionStore.baseDirectory.path, passphrase: nil, userAgent: UserAgentBuilder.makeASCIIUserAgent(), // oidcConfiguration: oidcConfiguration, - customSlidingSyncProxy: ServiceLocator.shared.settings.slidingSyncProxyURL?.absoluteString) + customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString) } // MARK: - Public diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index 9312ea995..b19264067 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -32,8 +32,8 @@ class BugReportService: NSObject, BugReportServiceProtocol { init(withBaseURL baseURL: URL, sentryURL: URL, - applicationId: String = ServiceLocator.shared.settings.bugReportApplicationId, - maxUploadSize: Int = ServiceLocator.shared.settings.bugReportMaxUploadSize, + applicationId: String, + maxUploadSize: Int, session: URLSession = .shared) { self.baseURL = baseURL self.sentryURL = sentryURL diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 74ced3929..bf038cc97 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -22,6 +22,8 @@ import UIKit class ClientProxy: ClientProxyProtocol { private let client: ClientProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol + private let appSettings: AppSettings + private var sessionVerificationControllerProxy: SessionVerificationControllerProxy? private let mediaLoader: MediaLoaderProtocol private let clientQueue: DispatchQueue @@ -56,9 +58,11 @@ class ClientProxy: ClientProxyProtocol { let callbacks = PassthroughSubject() - init(client: ClientProtocol, backgroundTaskService: BackgroundTaskServiceProtocol) async { + init(client: ClientProtocol, backgroundTaskService: BackgroundTaskServiceProtocol, appSettings: AppSettings) async { self.client = client self.backgroundTaskService = backgroundTaskService + self.appSettings = appSettings + clientQueue = .init(label: "ClientProxyQueue", attributes: .concurrent) mediaLoader = MediaLoader(client: client, clientQueue: clientQueue) @@ -382,7 +386,7 @@ class ClientProxy: ClientProxyProtocol { } private func startEncryptionSyncService() { - guard ServiceLocator.shared.settings.isEncryptionSyncEnabled else { + guard appSettings.isEncryptionSyncEnabled else { return } configureEncryptionSyncService() @@ -420,7 +424,7 @@ class ClientProxy: ClientProxyProtocol { } do { - let roomListService = try ServiceLocator.shared.settings.isEncryptionSyncEnabled ? client.roomListService() : client.roomListServiceWithEncryption() + let roomListService = try appSettings.isEncryptionSyncEnabled ? client.roomListService() : client.roomListServiceWithEncryption() roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in guard let self else { return } MXLog.info("Received room list update: \(state)") diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift index 8e9ee758c..db6ae3196 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift @@ -21,11 +21,14 @@ import UserNotifications class NotificationManager: NSObject, NotificationManagerProtocol { private let notificationCenter: UserNotificationCenterProtocol + private let appSettings: AppSettings + private var userSession: UserSessionProtocol? - init(notificationCenter: UserNotificationCenterProtocol = UNUserNotificationCenter.current()) { + init(notificationCenter: UserNotificationCenterProtocol, + appSettings: AppSettings) { self.notificationCenter = notificationCenter - super.init() + self.appSettings = appSettings } // MARK: NotificationManagerProtocol @@ -103,8 +106,8 @@ class NotificationManager: NSObject, NotificationManagerProtocol { pusherNotificationClientIdentifier: clientProxy.restorationToken?.pusherNotificationClientIdentifier) let configuration = try await PusherConfiguration(identifiers: .init(pushkey: deviceToken.base64EncodedString(), - appId: ServiceLocator.shared.settings.pusherAppId), - kind: .http(data: .init(url: ServiceLocator.shared.settings.pushGatewayBaseURL.absoluteString, + appId: appSettings.pusherAppId), + kind: .http(data: .init(url: appSettings.pushGatewayBaseURL.absoluteString, format: .eventIdOnly, defaultPayload: defaultPayload.toJsonString())), appDisplayName: "\(InfoPlistReader.main.bundleDisplayName) (iOS)", @@ -121,7 +124,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { } private func pusherProfileTag() -> String { - if let currentTag = ServiceLocator.shared.settings.pusherProfileTag { + if let currentTag = appSettings.pusherProfileTag { return currentTag } let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -130,7 +133,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { return String(chars[chars.index(chars.startIndex, offsetBy: offset)]) }.joined() - ServiceLocator.shared.settings.pusherProfileTag = newTag + appSettings.pusherProfileTag = newTag return newTag } } @@ -140,7 +143,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { extension NotificationManager: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { - guard ServiceLocator.shared.settings.enableInAppNotifications else { + guard appSettings.enableInAppNotifications else { return [] } guard let delegate else { diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift index 382fd95b3..e095b4e9b 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift @@ -42,6 +42,7 @@ protocol RoomMemberProxyProtocol: AnyObject { extension RoomMemberProxyProtocol { var permalink: URL? { - try? PermalinkBuilder.permalinkTo(userIdentifier: userID) + try? PermalinkBuilder.permalinkTo(userIdentifier: userID, + baseURL: ServiceLocator.shared.settings.permalinkBaseURL) } } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 0a6984497..40e47645a 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -528,7 +528,6 @@ class RoomProxy: RoomProxyProtocol { await Task.dispatch(on: .global()) { do { try self.room.acceptInvitation() - ServiceLocator.shared.analytics.trackJoinedRoom(isDM: self.room.isDirect(), isSpace: self.room.isSpace(), activeMemberCount: UInt(self.room.activeMembersCount())) return .success(()) } catch { return .failure(.failedAcceptingInvite) diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 447e89fc7..140f74a54 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -147,9 +147,11 @@ protocol RoomProxyProtocol { extension RoomProxyProtocol { var permalink: URL? { - if let canonicalAlias, let link = try? PermalinkBuilder.permalinkTo(roomAlias: canonicalAlias) { + if let canonicalAlias, let link = try? PermalinkBuilder.permalinkTo(roomAlias: canonicalAlias, + baseURL: ServiceLocator.shared.settings.permalinkBaseURL) { return link - } else if let link = try? PermalinkBuilder.permalinkTo(roomIdentifier: id) { + } else if let link = try? PermalinkBuilder.permalinkTo(roomIdentifier: id, + baseURL: ServiceLocator.shared.settings.permalinkBaseURL) { return link } else { MXLog.error("Failed to build permalink for Room: \(id)") diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 6d9fb9a80..dca83ebc6 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -80,7 +80,7 @@ enum RoomTimelineItemFixtures { isEditable: false, sender: .init(id: "", displayName: "Helena"), content: .init(body: "", - formattedBody: AttributedStringBuilder().fromHTML("Hol' up
New home office set up!
That's amazing! Congrats 🥳"))) + formattedBody: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL).fromHTML("Hol' up
New home office set up!
That's amazing! Congrats 🥳"))) ] /// A small chunk of events, containing 2 text items. diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index b5c562bcd..2c4ca073b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -24,6 +24,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { private let timelineProvider: RoomTimelineProviderProtocol private let timelineItemFactory: RoomTimelineItemFactoryProtocol private let mediaProvider: MediaProviderProtocol + private let appSettings: AppSettings private let serialDispatchQueue: DispatchQueue private var cancellables = Set() @@ -44,12 +45,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol { init(userId: String, roomProxy: RoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, - mediaProvider: MediaProviderProtocol) { + mediaProvider: MediaProviderProtocol, + appSettings: AppSettings) { self.userId = userId self.roomProxy = roomProxy timelineProvider = roomProxy.timelineProvider self.timelineItemFactory = timelineItemFactory self.mediaProvider = mediaProvider + self.appSettings = appSettings serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility) timelineProvider @@ -304,7 +307,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool { - if !ServiceLocator.shared.settings.shouldCollapseRoomStateEvents { + if !appSettings.shouldCollapseRoomStateEvents { return false } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift index 3d29ea90f..dbd5dcac2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift @@ -24,6 +24,7 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol { RoomTimelineController(userId: userId, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, - mediaProvider: mediaProvider) + mediaProvider: mediaProvider, + appSettings: ServiceLocator.shared.settings) } } diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index df437a257..c68b98e1f 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -139,7 +139,9 @@ class UserSessionStore: UserSessionStoreProtocol { return .failure(.failedSettingUpSession) } - let clientProxy = await ClientProxy(client: client, backgroundTaskService: backgroundTaskService) + let clientProxy = await ClientProxy(client: client, + backgroundTaskService: backgroundTaskService, + appSettings: ServiceLocator.shared.settings) return .success(clientProxy) } diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 40ef2d31c..8b455bed1 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -36,7 +36,9 @@ class UITestsAppCoordinator: AppCoordinatorProtocol { AppSettings.reset() ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(bugReportService: BugReportServiceMock()) - ServiceLocator.shared.register(analytics: Analytics(client: AnalyticsClientMock())) + ServiceLocator.shared.register(analytics: AnalyticsService(client: AnalyticsClientMock(), + appSettings: ServiceLocator.shared.settings, + bugReportService: ServiceLocator.shared.bugReportService)) } func start() { @@ -75,7 +77,8 @@ class MockScreen: Identifiable { switch id { case .login: let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = LoginScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy())) + let coordinator = LoginScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .serverConfirmationLogin: @@ -93,25 +96,30 @@ class MockScreen: Identifiable { case .serverSelection: let navigationStackCoordinator = NavigationStackCoordinator() let coordinator = ServerSelectionScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), - userIndicatorController: UserIndicatorControllerMock.default, + userIndicatorController: ServiceLocator.shared.userIndicatorController, isModallyPresented: true)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .serverSelectionNonModal: return ServerSelectionScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), - userIndicatorController: UserIndicatorControllerMock.default, + userIndicatorController: ServiceLocator.shared.userIndicatorController, isModallyPresented: false)) case .analyticsPrompt: - return AnalyticsPromptScreenCoordinator() + return AnalyticsPromptScreenCoordinator(analytics: ServiceLocator.shared.analytics, + termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL) case .analyticsSettingsScreen: let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = AnalyticsSettingsScreenCoordinator() + let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .authenticationFlow: let navigationStackCoordinator = NavigationStackCoordinator() let coordinator = AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(), - navigationStackCoordinator: navigationStackCoordinator) + navigationStackCoordinator: navigationStackCoordinator, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) retainedState.append(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator @@ -122,7 +130,8 @@ class MockScreen: Identifiable { deviceID: "ABCDEFGH") return SoftLogoutScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), credentials: credentials, - keyBackupNeeded: false)) + keyBackupNeeded: false, + userIndicatorController: ServiceLocator.shared.userIndicatorController)) case .waitlist: let navigationStackCoordinator = NavigationStackCoordinator() let credentials = WaitlistScreenCredentials(username: "alice", @@ -132,7 +141,7 @@ class MockScreen: Identifiable { homeserver: .mockMatrixDotOrg) let coordinator = WaitlistScreenCoordinator(parameters: .init(credentials: credentials, authenticationService: MockAuthenticationServiceProxy(), - userIndicatorController: UserIndicatorControllerMock.default)) + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .simpleRegular: @@ -144,7 +153,7 @@ class MockScreen: Identifiable { let session = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:matrix.org"), mediaProvider: MockMediaProvider()) let coordinator = HomeScreenCoordinator(parameters: .init(userSession: session, - attributedStringBuilder: AttributedStringBuilder(), + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL), bugReportService: BugReportServiceMock(), navigationStackCoordinator: navigationStackCoordinator, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher())) @@ -337,7 +346,8 @@ class MockScreen: Identifiable { navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, mediaProvider: MockMediaProvider(), - userDiscoveryService: UserDiscoveryServiceMock())) + userDiscoveryService: UserDiscoveryServiceMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomDetailsScreenWithRoomAvatar: @@ -356,7 +366,8 @@ class MockScreen: Identifiable { navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, mediaProvider: MockMediaProvider(), - userDiscoveryService: UserDiscoveryServiceMock())) + userDiscoveryService: UserDiscoveryServiceMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomDetailsScreenWithEmptyTopic: @@ -377,7 +388,8 @@ class MockScreen: Identifiable { navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, mediaProvider: MockMediaProvider(), - userDiscoveryService: UserDiscoveryServiceMock())) + userDiscoveryService: UserDiscoveryServiceMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomDetailsScreenWithInvite: @@ -394,7 +406,8 @@ class MockScreen: Identifiable { navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, mediaProvider: MockMediaProvider(), - userDiscoveryService: UserDiscoveryServiceMock())) + userDiscoveryService: UserDiscoveryServiceMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomDetailsScreenDmDetails: @@ -412,7 +425,8 @@ class MockScreen: Identifiable { navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, mediaProvider: MockMediaProvider(), - userDiscoveryService: UserDiscoveryServiceMock())) + userDiscoveryService: UserDiscoveryServiceMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomEditDetails, .roomEditDetailsReadOnly: @@ -427,7 +441,7 @@ class MockScreen: Identifiable { mediaProvider: MockMediaProvider(), navigationStackCoordinator: navigationStackCoordinator, roomProxy: roomProxy, - userIndicatorController: UserIndicatorControllerMock.default)) + userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomMembersListScreen: diff --git a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift index 63ea8ba69..0ae1ab250 100644 --- a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift +++ b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift @@ -26,7 +26,9 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol { AppSettings.reset() ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(bugReportService: BugReportServiceMock()) - ServiceLocator.shared.register(analytics: Analytics(client: AnalyticsClientMock())) + ServiceLocator.shared.register(analytics: AnalyticsService(client: AnalyticsClientMock(), + appSettings: ServiceLocator.shared.settings, + bugReportService: ServiceLocator.shared.bugReportService)) } func start() { } diff --git a/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift b/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift index 8f0e6deee..25d252a92 100644 --- a/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift @@ -20,7 +20,6 @@ import XCTest @MainActor class AnalyticsSettingsScreenViewModelTests: XCTestCase { - private var appSettings: AppSettings { ServiceLocator.shared.settings } private var viewModel: AnalyticsSettingsScreenViewModelProtocol! private var context: AnalyticsSettingsScreenViewModelType.Context! @@ -28,9 +27,12 @@ class AnalyticsSettingsScreenViewModelTests: XCTestCase { AppSettings.reset() let analyticsClient = AnalyticsClientMock() analyticsClient.isRunning = false - ServiceLocator.shared.register(analytics: Analytics(client: analyticsClient)) + ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient, + appSettings: ServiceLocator.shared.settings, + bugReportService: ServiceLocator.shared.bugReportService)) - viewModel = AnalyticsSettingsScreenViewModel() + viewModel = AnalyticsSettingsScreenViewModel(appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics) context = viewModel.context } @@ -39,13 +41,13 @@ class AnalyticsSettingsScreenViewModelTests: XCTestCase { } func testOptIn() { - appSettings.analyticsConsentState = .optedOut + ServiceLocator.shared.settings.analyticsConsentState = .optedOut context.send(viewAction: .toggleAnalytics) XCTAssertTrue(context.enableAnalytics) } func testOptOut() { - appSettings.analyticsConsentState = .optedIn + ServiceLocator.shared.settings.analyticsConsentState = .optedIn context.send(viewAction: .toggleAnalytics) XCTAssertFalse(context.enableAnalytics) } diff --git a/UnitTests/Sources/AnalyticsTests.swift b/UnitTests/Sources/AnalyticsTests.swift index 12f7c7322..4b5c35c6e 100644 --- a/UnitTests/Sources/AnalyticsTests.swift +++ b/UnitTests/Sources/AnalyticsTests.swift @@ -31,7 +31,9 @@ class AnalyticsTests: XCTestCase { ServiceLocator.shared.register(bugReportService: bugReportService) analyticsClient = AnalyticsClientMock() analyticsClient.isRunning = false - ServiceLocator.shared.register(analytics: Analytics(client: analyticsClient)) + ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient, + appSettings: ServiceLocator.shared.settings, + bugReportService: ServiceLocator.shared.bugReportService)) } func testAnalyticsPromptNewUser() { @@ -70,7 +72,7 @@ class AnalyticsTests: XCTestCase { XCTAssertEqual(ServiceLocator.shared.settings.analyticsConsentState, .unknown) XCTAssertFalse(ServiceLocator.shared.analytics.isEnabled) XCTAssertFalse(ServiceLocator.shared.analytics.isRunning) - XCTAssertFalse(analyticsClient.startCalled) + XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled) XCTAssertFalse(bugReportService.startCalled) } @@ -97,7 +99,7 @@ class AnalyticsTests: XCTestCase { XCTAssertEqual(appSettings.analyticsConsentState, .optedIn) XCTAssertTrue(ServiceLocator.shared.analytics.isEnabled) // Analytics client and the bug report service should have been started - XCTAssertTrue(analyticsClient.startCalled) + XCTAssertTrue(analyticsClient.startAnalyticsConfigurationCalled) XCTAssertTrue(bugReportService.startCalled) } @@ -107,7 +109,7 @@ class AnalyticsTests: XCTestCase { // Analytics should not start XCTAssertFalse(ServiceLocator.shared.analytics.isEnabled) ServiceLocator.shared.analytics.startIfEnabled() - XCTAssertFalse(analyticsClient.startCalled) + XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled) XCTAssertFalse(bugReportService.startCalled) } @@ -117,7 +119,7 @@ class AnalyticsTests: XCTestCase { // Analytics should start XCTAssertTrue(ServiceLocator.shared.analytics.isEnabled) ServiceLocator.shared.analytics.startIfEnabled() - XCTAssertTrue(analyticsClient.startCalled) + XCTAssertTrue(analyticsClient.startAnalyticsConfigurationCalled) XCTAssertTrue(bugReportService.startCalled) } @@ -172,7 +174,7 @@ class AnalyticsTests: XCTestCase { numFavouriteRooms: nil, numSpaces: nil, allChatsActiveFilter: nil)) - client.start() + client.start(analyticsConfiguration: ServiceLocator.shared.settings.analyticsConfiguration) XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.") XCTAssertEqual(client.pendingUserProperties?.ftueUseCaseSelection, .PersonalMessaging, "The use case selection should match.") diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift index 0634310f8..0010eb6e4 100644 --- a/UnitTests/Sources/AttributedStringBuilderTests.swift +++ b/UnitTests/Sources/AttributedStringBuilderTests.swift @@ -18,7 +18,7 @@ import XCTest class AttributedStringBuilderTests: XCTestCase { - let attributedStringBuilder = AttributedStringBuilder() + let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL) let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2) func testRenderHTMLStringWithHeaders() { diff --git a/UnitTests/Sources/AttributedStringTests.swift b/UnitTests/Sources/AttributedStringTests.swift index 457253a7d..c779326d7 100644 --- a/UnitTests/Sources/AttributedStringTests.swift +++ b/UnitTests/Sources/AttributedStringTests.swift @@ -21,7 +21,8 @@ class AttributedStringTests: XCTestCase { func testReplacingFontWithPresentationIntent() { // Given a string parsed from HTML that contains specific fixed size fonts. let boldString = "Bold" - guard let originalString = AttributedStringBuilder().fromHTML("Normal \(boldString) Normal.") else { + guard let originalString = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL) + .fromHTML("Normal \(boldString) Normal.") else { XCTFail("The attributed string should be built from the HTML.") return } diff --git a/UnitTests/Sources/BugReportServiceTests.swift b/UnitTests/Sources/BugReportServiceTests.swift index 205e6152b..015d9d206 100644 --- a/UnitTests/Sources/BugReportServiceTests.swift +++ b/UnitTests/Sources/BugReportServiceTests.swift @@ -50,6 +50,7 @@ class BugReportServiceTests: XCTestCase { let service = BugReportService(withBaseURL: URL(staticString: "https://www.example.com"), sentryURL: URL(staticString: "https://1234@sentry.com/1234"), applicationId: "mock_app_id", + maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, session: .mock) XCTAssertFalse(service.crashedLastRun) } @@ -58,6 +59,7 @@ class BugReportServiceTests: XCTestCase { let service = BugReportService(withBaseURL: URL(staticString: "https://www.example.com"), sentryURL: URL(staticString: "https://1234@sentry.com/1234"), applicationId: "mock_app_id", + maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, session: .mock) let bugReport = BugReport(userID: "@mock:client.com", diff --git a/UnitTests/Sources/CreateRoomViewModelTests.swift b/UnitTests/Sources/CreateRoomViewModelTests.swift index 29c68bec4..c01631d79 100644 --- a/UnitTests/Sources/CreateRoomViewModelTests.swift +++ b/UnitTests/Sources/CreateRoomViewModelTests.swift @@ -37,7 +37,11 @@ class CreateRoomScreenViewModelTests: XCTestCase { userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) let parameters = CreateRoomFlowParameters() usersSubject.send([.mockAlice, .mockBob, .mockCharlie]) - let viewModel = CreateRoomViewModel(userSession: userSession, userIndicatorController: nil, createRoomParameters: .init(parameters), selectedUsers: usersSubject.asCurrentValuePublisher()) + let viewModel = CreateRoomViewModel(userSession: userSession, + createRoomParameters: .init(parameters), + selectedUsers: usersSubject.asCurrentValuePublisher(), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: nil) self.viewModel = viewModel viewModel.actions.sink { [weak self] action in diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 4b95fdced..5c59f32ff 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -32,8 +32,10 @@ class HomeScreenViewModelTests: XCTestCase { clientProxy = MockClientProxy(userID: "@mock:client.com") viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()), - attributedStringBuilder: AttributedStringBuilder(), - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher()) + attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL), + selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + userIndicatorController: ServiceLocator.shared.userIndicatorController) } func testSelectRoom() async throws { diff --git a/UnitTests/Sources/InviteUsersViewModelTests.swift b/UnitTests/Sources/InviteUsersViewModelTests.swift index 8b30b6d4b..8453f0da7 100644 --- a/UnitTests/Sources/InviteUsersViewModelTests.swift +++ b/UnitTests/Sources/InviteUsersViewModelTests.swift @@ -89,6 +89,7 @@ class InviteUsersScreenViewModelTests: XCTestCase { let viewModel = InviteUsersScreenViewModel(selectedUsers: usersSubject.asCurrentValuePublisher(), roomType: roomType, mediaProvider: MockMediaProvider(), userDiscoveryService: userDiscoveryService, + appSettings: ServiceLocator.shared.settings, userIndicatorController: UserIndicatorControllerMock()) viewModel.state.usersSection = .init(type: .suggestions, users: [.mockAlice, .mockBob, .mockCharlie]) self.viewModel = viewModel diff --git a/UnitTests/Sources/InvitesScreenViewModelTests.swift b/UnitTests/Sources/InvitesScreenViewModelTests.swift index 24a18ec93..eb3b129d9 100644 --- a/UnitTests/Sources/InvitesScreenViewModelTests.swift +++ b/UnitTests/Sources/InvitesScreenViewModelTests.swift @@ -82,6 +82,9 @@ class InvitesScreenViewModelTests: XCTestCase { clientProxy.roomSummaryProvider = summaryProvider } - viewModel = InvitesScreenViewModel(userSession: userSession) + viewModel = InvitesScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: ServiceLocator.shared.userIndicatorController) } } diff --git a/UnitTests/Sources/LoginViewModelTests.swift b/UnitTests/Sources/LoginViewModelTests.swift index b2286e0b7..113e60edf 100644 --- a/UnitTests/Sources/LoginViewModelTests.swift +++ b/UnitTests/Sources/LoginViewModelTests.swift @@ -25,7 +25,7 @@ class LoginViewModelTests: XCTestCase { var context: LoginScreenViewModelType.Context! @MainActor override func setUp() async throws { - viewModel = LoginScreenViewModel(homeserver: defaultHomeserver) + viewModel = LoginScreenViewModel(homeserver: defaultHomeserver, slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL) context = viewModel.context } diff --git a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift index ad17e8e8c..04e03e2bf 100644 --- a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift +++ b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift @@ -34,7 +34,7 @@ final class NotificationManagerTests: XCTestCase { override func setUp() { AppSettings.reset() - notificationManager = NotificationManager(notificationCenter: notificationCenter) + notificationManager = NotificationManager(notificationCenter: notificationCenter, appSettings: appSettings) notificationManager.start() notificationManager.setUserSession(mockUserSession) } diff --git a/UnitTests/Sources/PermalinkBuilderTests.swift b/UnitTests/Sources/PermalinkBuilderTests.swift index 8a2f36f24..015f9c874 100644 --- a/UnitTests/Sources/PermalinkBuilderTests.swift +++ b/UnitTests/Sources/PermalinkBuilderTests.swift @@ -30,7 +30,7 @@ class PermalinkBuilderTests: XCTestCase { let userId = "@abcdefghijklmnopqrstuvwxyz1234567890._-=/:matrix.org" do { - let permalink = try PermalinkBuilder.permalinkTo(userIdentifier: userId) + let permalink = try PermalinkBuilder.permalinkTo(userIdentifier: userId, baseURL: appSettings.permalinkBaseURL) XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/\(userId)")) } catch { XCTFail("User identifier must be valid: \(error)") @@ -39,7 +39,7 @@ class PermalinkBuilderTests: XCTestCase { func testInvalidUserIdentifier() { do { - _ = try PermalinkBuilder.permalinkTo(userIdentifier: "This1sN0tV4lid!@#$%^&*()") + _ = try PermalinkBuilder.permalinkTo(userIdentifier: "This1sN0tV4lid!@#$%^&*()", baseURL: appSettings.permalinkBaseURL) XCTFail("A permalink should not be created.") } catch { XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidUserIdentifier) @@ -50,7 +50,7 @@ class PermalinkBuilderTests: XCTestCase { let roomId = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org" do { - let permalink = try PermalinkBuilder.permalinkTo(roomIdentifier: roomId) + let permalink = try PermalinkBuilder.permalinkTo(roomIdentifier: roomId, baseURL: appSettings.permalinkBaseURL) XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org")) } catch { XCTFail("Room identifier must be valid: \(error)") @@ -59,7 +59,7 @@ class PermalinkBuilderTests: XCTestCase { func testInvalidRoomIdentifier() { do { - _ = try PermalinkBuilder.permalinkTo(roomIdentifier: "This1sN0tV4lid!@#$%^&*()") + _ = try PermalinkBuilder.permalinkTo(roomIdentifier: "This1sN0tV4lid!@#$%^&*()", baseURL: appSettings.permalinkBaseURL) XCTFail("A permalink should not be created.") } catch { XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidRoomIdentifier) @@ -70,7 +70,7 @@ class PermalinkBuilderTests: XCTestCase { let roomAlias = "#abcdefghijklmnopqrstuvwxyz-_.1234567890:matrix.org" do { - let permalink = try PermalinkBuilder.permalinkTo(roomAlias: roomAlias) + let permalink = try PermalinkBuilder.permalinkTo(roomAlias: roomAlias, baseURL: appSettings.permalinkBaseURL) XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/%23abcdefghijklmnopqrstuvwxyz-_.1234567890%3Amatrix.org")) } catch { XCTFail("Room alias must be valid: \(error)") @@ -79,7 +79,7 @@ class PermalinkBuilderTests: XCTestCase { func testInvalidRoomAlias() { do { - _ = try PermalinkBuilder.permalinkTo(roomAlias: "This1sN0tV4lid!@#$%^&*()") + _ = try PermalinkBuilder.permalinkTo(roomAlias: "This1sN0tV4lid!@#$%^&*()", baseURL: appSettings.permalinkBaseURL) XCTFail("A permalink should not be created.") } catch { XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidRoomAlias) @@ -91,7 +91,7 @@ class PermalinkBuilderTests: XCTestCase { let roomId = "!abcdefghijklmnopqrstuvwxyz1234567890:matrix.org" do { - let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventId, roomIdentifier: roomId) + let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventId, roomIdentifier: roomId, baseURL: appSettings.permalinkBaseURL) XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org/%24abcdefghijklmnopqrstuvwxyz1234567890")) } catch { XCTFail("Room and event identifiers must be valid: \(error)") @@ -100,7 +100,7 @@ class PermalinkBuilderTests: XCTestCase { func testInvalidEventIdentifier() { do { - _ = try PermalinkBuilder.permalinkTo(eventIdentifier: "This1sN0tV4lid!@#$%^&*()", roomIdentifier: "") + _ = try PermalinkBuilder.permalinkTo(eventIdentifier: "This1sN0tV4lid!@#$%^&*()", roomIdentifier: "", baseURL: appSettings.permalinkBaseURL) XCTFail("A permalink should not be created.") } catch { XCTAssertEqual(error as? PermalinkBuilderError, PermalinkBuilderError.invalidEventIdentifier) @@ -109,18 +109,18 @@ class PermalinkBuilderTests: XCTestCase { func testPermalinkDetection() { var url = URL(staticString: "https://www.matrix.org") - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url), nil) + XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), nil) url = URL(staticString: "https://matrix.to/#/@bob:matrix.org?via=matrix.org") - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url), PermalinkType.userIdentifier("@bob:matrix.org")) + XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.userIdentifier("@bob:matrix.org")) url = URL(staticString: "https://matrix.to/#/!roomidentifier:matrix.org?via=matrix.org") - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url), PermalinkType.roomIdentifier("!roomidentifier:matrix.org")) + XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.roomIdentifier("!roomidentifier:matrix.org")) url = URL(staticString: "https://matrix.to/#/%23roomalias:matrix.org?via=matrix.org") - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url), PermalinkType.roomAlias("#roomalias:matrix.org")) + XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.roomAlias("#roomalias:matrix.org")) url = URL(staticString: "https://matrix.to/#/!roomidentifier:matrix.org/$eventidentifier?via=matrix.org") - XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url), PermalinkType.event(roomIdentifier: "!roomidentifier:matrix.org", eventIdentifier: "$eventidentifier")) + XCTAssertEqual(PermalinkBuilder.detectPermalink(in: url, baseURL: appSettings.permalinkBaseURL), PermalinkType.event(roomIdentifier: "!roomidentifier:matrix.org", eventIdentifier: "$eventidentifier")) } } diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index 76cbc2c65..ac7d161ba 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -27,7 +27,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { override func setUp() { roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", joinedMembersCount: 0)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) AppSettings.reset() } @@ -35,7 +38,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testLeaveRoomTappedWhenPublic() async { let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() context.send(viewAction: .processTapLeave) XCTAssertEqual(context.leaveRoomAlertItem?.state, .public) @@ -45,7 +51,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testLeaveRoomTappedWhenRoomNotPublic() async { let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() context.send(viewAction: .processTapLeave) XCTAssertEqual(context.leaveRoomAlertItem?.state, .private) @@ -89,7 +98,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let recipient = RoomMemberProxyMock.mockDan let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) } @@ -102,7 +114,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) @@ -124,7 +139,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) @@ -147,7 +165,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) @@ -169,7 +190,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) await context.nextViewState() XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient)) @@ -191,7 +215,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { members: mockedMembers, memberForID: .mockOwner(allowedStateEvents: [], canInviteUsers: false), activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -201,7 +228,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testInvitePeople() async { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers, activeMembersCount: mockedMembers.count)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -226,7 +256,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomAvatar]) let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -240,7 +273,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomName]) let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -254,7 +290,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomTopic]) let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -268,7 +307,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: []) let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() @@ -281,7 +323,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testCannotEditDirectRoom() async { let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isPublic: false, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", roomProxy: roomProxyMock, mediaProvider: MockMediaProvider()) + viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", + roomProxy: roomProxyMock, + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) _ = await context.$viewState.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main).values.first() diff --git a/UnitTests/Sources/RoomFlowCoordinatorTests.swift b/UnitTests/Sources/RoomFlowCoordinatorTests.swift index b57748e72..05eba3428 100644 --- a/UnitTests/Sources/RoomFlowCoordinatorTests.swift +++ b/UnitTests/Sources/RoomFlowCoordinatorTests.swift @@ -39,6 +39,8 @@ class RoomFlowCoordinatorTests: XCTestCase { navigationStackCoordinator: navigationStackCoordinator, navigationSplitCoordinator: navigationSplitCoordinator, emojiProvider: EmojiProvider(), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController) } diff --git a/UnitTests/Sources/RoomMembersListViewModelTests.swift b/UnitTests/Sources/RoomMembersListViewModelTests.swift index 461f94cd6..bfb746f5b 100644 --- a/UnitTests/Sources/RoomMembersListViewModelTests.swift +++ b/UnitTests/Sources/RoomMembersListViewModelTests.swift @@ -76,6 +76,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase { private func setup(with members: [RoomMemberProxyMock]) { viewModel = .init(roomProxy: RoomProxyMock(with: .init(displayName: "test", members: members, joinedMembersCount: members.filter { $0.membership == .join }.count)), - mediaProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) } } diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index 181a3812a..168f4bb67 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -45,7 +45,10 @@ class RoomScreenViewModelTests: XCTestCase { timelineController.timelineItems = items let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: ""))) + roomProxy: RoomProxyMock(with: .init(displayName: "")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Then the messages should be grouped together. XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .first, "Nothing should prevent the first message from being grouped.") @@ -75,7 +78,10 @@ class RoomScreenViewModelTests: XCTestCase { timelineController.timelineItems = items let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: ""))) + roomProxy: RoomProxyMock(with: .init(displayName: "")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Then the messages should be grouped by sender. XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .single, "A message should not be grouped when the sender changes.") @@ -103,7 +109,10 @@ class RoomScreenViewModelTests: XCTestCase { timelineController.timelineItems = items let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: ""))) + roomProxy: RoomProxyMock(with: .init(displayName: "")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Then the first message should not be grouped but the other two should. XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .single, "When the first message has reactions it should not be grouped.") @@ -128,7 +137,10 @@ class RoomScreenViewModelTests: XCTestCase { timelineController.timelineItems = items let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: ""))) + roomProxy: RoomProxyMock(with: .init(displayName: "")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Then the first and second messages should be grouped and the last one should not. XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .first, "Nothing should prevent the first message from being grouped.") @@ -153,7 +165,10 @@ class RoomScreenViewModelTests: XCTestCase { timelineController.timelineItems = items let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(displayName: ""))) + roomProxy: RoomProxyMock(with: .init(displayName: "")), + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Then the messages should be grouped together. XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .first, "Nothing should prevent the first message from being grouped.") @@ -172,6 +187,8 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, userIndicatorController: userIndicatorControllerMock) viewModel.callback = { action in switch action { @@ -204,7 +221,10 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, userIndicatorController: userIndicatorControllerMock) + viewModel.callback = { action in switch action { case .displayRoomMemberDetails(let member): @@ -236,6 +256,8 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, userIndicatorController: userIndicatorControllerMock) viewModel.callback = { _ in XCTFail("Should not receive any action") @@ -256,7 +278,10 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: roomProxyMock) + roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Test viewModel.context.send(viewAction: .retrySend(transactionID: "test retry send id")) @@ -272,7 +297,10 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: roomProxyMock) + roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Test viewModel.context.send(viewAction: .retrySend(transactionID: nil)) @@ -287,7 +315,10 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: roomProxyMock) + roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Test viewModel.context.send(viewAction: .cancelSend(transactionID: "test cancel send id")) @@ -303,7 +334,10 @@ class RoomScreenViewModelTests: XCTestCase { let viewModel = RoomScreenViewModel(timelineController: timelineController, mediaProvider: MockMediaProvider(), - roomProxy: roomProxyMock) + roomProxy: roomProxyMock, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: userIndicatorControllerMock) // Test viewModel.context.send(viewAction: .cancelSend(transactionID: nil)) diff --git a/UnitTests/Sources/ServerSelectionViewModelTests.swift b/UnitTests/Sources/ServerSelectionViewModelTests.swift index 5964f88ee..9ab75d8df 100644 --- a/UnitTests/Sources/ServerSelectionViewModelTests.swift +++ b/UnitTests/Sources/ServerSelectionViewModelTests.swift @@ -28,7 +28,9 @@ class ServerSelectionViewModelTests: XCTestCase { var context: ServerSelectionScreenViewModelType.Context! @MainActor override func setUp() { - viewModel = ServerSelectionScreenViewModel(homeserverAddress: "", isModallyPresented: true) + viewModel = ServerSelectionScreenViewModel(homeserverAddress: "", + slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL, + isModallyPresented: true) context = viewModel.context } diff --git a/UnitTests/Sources/SettingsViewModelTests.swift b/UnitTests/Sources/SettingsViewModelTests.swift index 9a75f1fae..f26c32c7a 100644 --- a/UnitTests/Sources/SettingsViewModelTests.swift +++ b/UnitTests/Sources/SettingsViewModelTests.swift @@ -26,7 +26,7 @@ class SettingsScreenViewModelTests: XCTestCase { @MainActor override func setUpWithError() throws { let userSession = MockUserSession(clientProxy: MockClientProxy(userID: ""), mediaProvider: MockMediaProvider()) - viewModel = SettingsScreenViewModel(withUserSession: userSession) + viewModel = SettingsScreenViewModel(userSession: userSession, appSettings: ServiceLocator.shared.settings) context = viewModel.context } diff --git a/UnitTests/Sources/StartChatViewModelTests.swift b/UnitTests/Sources/StartChatViewModelTests.swift index afe797959..f97cb52e0 100644 --- a/UnitTests/Sources/StartChatViewModelTests.swift +++ b/UnitTests/Sources/StartChatViewModelTests.swift @@ -34,7 +34,11 @@ class StartChatScreenViewModelTests: XCTestCase { userDiscoveryService.fetchSuggestionsReturnValue = .success([]) userDiscoveryService.searchProfilesWithReturnValue = .success([]) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) - viewModel = StartChatScreenViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService) + viewModel = StartChatScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: nil, + userDiscoveryService: userDiscoveryService) AppSettings.reset() ServiceLocator.shared.settings.userSuggestionsEnabled = true