From 6f974a44221f23125fa0f7f4326b56133a67676b Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 16 Dec 2022 10:02:22 +0200 Subject: [PATCH] Resetting the app after deletion and cleanup settings (#372) * Fixes #361 - Reset everything if the app has been deleted since the last run * Cleaned up and renamed the ElementSettings to ApplicationSettings. Removed its singleton and moved it to the service locator. * Moved the ApplicationSettings to the Application folder * Merged together the BuildSettings and ApplicationSettings * Reset the UserDefaults too when resetting the application state * Rename ServiceLocator.applicationSettings to just settings * Fix indentation * Rename ApplicationSettings to AppSettings * Various tweaks following code review * Fix unit tests --- ElementX.xcodeproj/project.pbxproj | 16 +- .../Sources/Application/AppCoordinator.swift | 26 +++- .../Sources/Application/AppSettings.swift | 138 ++++++++++++++++++ .../Sources/Application/BuildSettings.swift | 80 ---------- .../Sources/Application/ServiceLocator.swift | 6 + ElementX/Sources/Other/ElementSettings.swift | 82 ----------- ElementX/Sources/Other/PermalinkBuilder.swift | 6 +- .../AnalyticsPromptCoordinator.swift | 2 +- .../AnalyticsPromptModels.swift | 2 +- .../View/AnalyticsPrompt.swift | 2 +- .../AuthenticationCoordinator.swift | 2 +- .../ServerSelectionViewModel.swift | 6 +- .../BugReport/BugReportViewModel.swift | 2 +- .../Screens/RoomScreen/View/RoomScreen.swift | 2 +- .../RoomScreen/View/Style/TimelineStyle.swift | 2 +- .../Settings/SettingsCoordinator.swift | 4 +- .../Screens/Settings/SettingsModels.swift | 4 +- .../Screens/Settings/SettingsViewModel.swift | 2 - .../Settings/View/SettingsScreen.swift | 26 +--- .../Services/Analytics/Analytics.swift | 16 +- .../Analytics/PHGPostHogConfiguration.swift | 2 +- .../AuthenticationServiceProxy.swift | 2 +- .../Services/BugReport/BugReportService.swift | 2 +- .../Sources/Services/Client/ClientProxy.swift | 2 +- .../Manager/NotificationManager.swift | 10 +- .../UserSession/UserSessionStore.swift | 6 + .../UserSessionStoreProtocol.swift | 3 + .../UITests/UITestsAppCoordinator.swift | 4 + UITests/Sources/SettingsUITests.swift | 7 +- .../de-DE-iPad-9th-generation.settings.png | 4 +- .../Application/de-DE-iPhone-14.settings.png | 4 +- .../en-GB-iPad-9th-generation.settings.png | 4 +- .../Application/en-GB-iPhone-14.settings.png | 4 +- .../fr-FR-iPad-9th-generation.settings.png | 4 +- .../Application/fr-FR-iPhone-14.settings.png | 4 +- UITests/SupportingFiles/target.yml | 2 +- UnitTests/Sources/AnalyticsTests.swift | 14 +- UnitTests/Sources/PermalinkBuilderTests.swift | 16 +- .../Sources/SettingsViewModelTests.swift | 11 -- 39 files changed, 259 insertions(+), 272 deletions(-) create mode 100644 ElementX/Sources/Application/AppSettings.swift delete mode 100644 ElementX/Sources/Application/BuildSettings.swift delete mode 100644 ElementX/Sources/Other/ElementSettings.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index e130176ff..47d5231db 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 0F3F2FDD4021A25A0D57F801 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; }; 0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */; }; 1281625B25371BE53D36CB3A /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */; }; + 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; 132D241B09F9044711FD70A5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; }; 13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */; }; 13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; }; @@ -86,7 +87,6 @@ 28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */; }; 290FDB0FFDC2F1DDF660343E /* TestMeasurementParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */; }; 297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; }; - 29E20505F321071E8375F99B /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; }; 29EE1791E0AFA1ABB7F23D2F /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; }; 2ABF11717C64054CEF2819A3 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */; }; 2B9AEEC12B1BBE5BD61D0F5E /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3429142FE11930422E7CC1A0 /* UserSessionFlowCoordinatorStateMachine.swift */; }; @@ -269,6 +269,7 @@ 91DFCB641FBA03EE2DA0189E /* FilePreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB27E1BE894F9F9F0134372 /* FilePreviewScreen.swift */; }; 9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; }; 92B95779840CD749117B3615 /* EmojiMartStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38AE3617D7619EF30CDD229 /* EmojiMartStore.swift */; }; + 9377D6BE1511E1529EA1662B /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; 93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; }; 93BA4A81B6D893271101F9F0 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; }; 9462C62798F47E39DCC182D2 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA89A2DD51B6BBE1DA55E263 /* Application.swift */; }; @@ -277,7 +278,6 @@ 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; }; 968A5B890004526AB58A217C /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; }; 97189E495F0E47805D1868DB /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 527578916BD388A09F5A8036 /* DTCoreText */; }; - 9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1027BB9A852F445B7623897F /* ElementSettings.swift */; }; 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */; }; 97CECF91D68235F1D13598D7 /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */; }; 981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */; }; @@ -369,7 +369,6 @@ C8E82786DE1B6A400DA9BA25 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289FA233E896FBC5956C67E0 /* RoomTimelineItemProperties.swift */; }; C94A6048C654B01163AE1BF1 /* VideoPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */; }; CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; }; - CA9558C0B40C1EE2AD00124A /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; }; CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; }; CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; }; CB498F4E27AA0545DCEF0F6F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; }; @@ -533,7 +532,6 @@ 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = ""; }; 0EE9EAF0309A2A1D67D8FAF5 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sv; path = sv.lproj/Localizable.stringsdict; sourceTree = ""; }; 0F52BF30D12BA3BD3D3DBB8F /* ServerSelectionViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelProtocol.swift; sourceTree = ""; }; - 1027BB9A852F445B7623897F /* ElementSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementSettings.swift; sourceTree = ""; }; 1059E2AE7878CF7820592637 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = ""; }; 105D16E7DB0CCE9526612BDD /* bn-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bn-IN"; path = "bn-IN.lproj/Localizable.strings"; sourceTree = ""; }; @@ -583,7 +581,6 @@ 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = ""; }; 2583416C8974272ADBADDBE1 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-TW"; path = "zh-TW.lproj/Localizable.stringsdict"; sourceTree = ""; }; 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = ""; }; - 263B3B811C2B900F12C6F695 /* BuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSettings.swift; sourceTree = ""; }; 26C4D226FCD20BAC53F1E092 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/Localizable.strings; sourceTree = ""; }; 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; @@ -836,6 +833,7 @@ AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskServiceProtocol.swift; sourceTree = ""; }; AB785716B9212C093704E767 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = ""; }; AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = ""; }; + AC3F82523D6F48B926D6AF68 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = ""; }; AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; @@ -1978,7 +1976,7 @@ 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */, C75EF87651B00A176AB08E97 /* AppDelegate.swift */, CA89A2DD51B6BBE1DA55E263 /* Application.swift */, - 263B3B811C2B900F12C6F695 /* BuildSettings.swift */, + AC3F82523D6F48B926D6AF68 /* AppSettings.swift */, B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */, 57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */, 780F74C73E826685A9DB289B /* Navigation */, @@ -2075,7 +2073,6 @@ E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */, 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */, E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */, - 1027BB9A852F445B7623897F /* ElementSettings.swift */, 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */, 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */, 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */, @@ -2808,6 +2805,7 @@ A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */, 4FF90E2242DBD596E1ED2E27 /* AppCoordinatorStateMachine.swift in Sources */, 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */, + 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */, 9462C62798F47E39DCC182D2 /* Application.swift in Sources */, 74604ACFDBE7F54260E7B617 /* ApplicationProtocol.swift in Sources */, 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */, @@ -2830,7 +2828,6 @@ 172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */, 86C2E93920FD15AD17E193A9 /* BugReportViewModel.swift in Sources */, 187E18F21EF4DA244E436E58 /* BugReportViewModelProtocol.swift in Sources */, - CA9558C0B40C1EE2AD00124A /* BuildSettings.swift in Sources */, E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */, 6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */, 1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */, @@ -2841,7 +2838,6 @@ 1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */, EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */, FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */, - 9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */, D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */, 7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */, 7361B011A79BF723D8C9782B /* EmojiCategory.swift in Sources */, @@ -3081,10 +3077,10 @@ files = ( 97CECF91D68235F1D13598D7 /* AnalyticsConfiguration.swift in Sources */, 8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */, + 9377D6BE1511E1529EA1662B /* AppSettings.swift in Sources */, 7405B4824D45BA7C3D943E76 /* Application.swift in Sources */, ACF094CF3BF02DBFA6DFDE60 /* AuthenticationCoordinatorUITests.swift in Sources */, 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */, - 29E20505F321071E8375F99B /* BuildSettings.swift in Sources */, 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */, E3C328EF20C4B3326D263BCD /* FileManager.swift in Sources */, 9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index bc3dc4d8b..77ef2d823 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -52,23 +52,30 @@ class AppCoordinator: AppCoordinatorProtocol { init() { navigationRootCoordinator = NavigationRootCoordinator() + + Self.setupServiceLocator(navigationRootCoordinator: navigationRootCoordinator) + Self.setupLogging() + stateMachine = AppCoordinatorStateMachine() - bugReportService = BugReportService(withBaseURL: BuildSettings.bugReportServiceBaseURL, sentryURL: BuildSettings.bugReportSentryURL) + bugReportService = BugReportService(withBaseURL: ServiceLocator.shared.settings.bugReportServiceBaseURL, sentryURL: ServiceLocator.shared.settings.bugReportSentryURL) navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator()) - ServiceLocator.shared.register(userNotificationController: UserNotificationController(rootCoordinator: navigationRootCoordinator)) - backgroundTaskService = UIKitBackgroundTaskService { UIApplication.shared } userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService) - setupStateMachine() + // Reset everything if the app has been deleted since the previous run + if !ServiceLocator.shared.settings.hasAppLaunchedOnce { + AppSettings.reset() + userSessionStore.reset() + ServiceLocator.shared.settings.hasAppLaunchedOnce = true + } - setupLogging() + setupStateMachine() Bundle.elementFallbackLanguage = "en" @@ -91,7 +98,12 @@ class AppCoordinator: AppCoordinatorProtocol { // MARK: - Private - private func setupLogging() { + private static func setupServiceLocator(navigationRootCoordinator: NavigationRootCoordinator) { + ServiceLocator.shared.register(userNotificationController: UserNotificationController(rootCoordinator: navigationRootCoordinator)) + ServiceLocator.shared.register(appSettings: AppSettings()) + } + + private static func setupLogging() { let loggerConfiguration = MXLogConfiguration() loggerConfiguration.maxLogFilesCount = 10 @@ -270,7 +282,7 @@ class AppCoordinator: AppCoordinatorProtocol { } private func configureNotificationManager() { - guard BuildSettings.enableNotifications else { + guard ServiceLocator.shared.settings.enableNotifications else { return } guard notificationManager == nil else { diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift new file mode 100644 index 000000000..9e3873cd7 --- /dev/null +++ b/ElementX/Sources/Application/AppSettings.swift @@ -0,0 +1,138 @@ +// +// 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 + +/// Store Element specific app settings. +final class AppSettings: ObservableObject { + private enum UserDefaultsKeys: String { + case hasAppLaunchedOnce + case timelineStyle + case enableAnalytics + case isIdentifiedForAnalytics + case slidingSyncProxyBaseURLString + case enableInAppNotifications + case pusherProfileTag + } + + private static var suiteName: String = InfoPlistReader.target.appGroupIdentifier + + /// UserDefaults to be used on reads and writes. + private static var store: UserDefaults! = UserDefaults(suiteName: suiteName) + + static func reset() { + store.removePersistentDomain(forName: suiteName) + } + + static func configureWithSuiteName(_ name: String) { + suiteName = name + + guard let userDefaults = UserDefaults(suiteName: name) else { + fatalError("Fail to load shared UserDefaults") + } + + store = userDefaults + } + + // MARK: - Application + + /// Simple flag to check if app has been deleted between runs. + /// Used to clear data stored in the shared container and keychain + @AppStorage(UserDefaultsKeys.hasAppLaunchedOnce.rawValue, store: store) + var hasAppLaunchedOnce = false + + let defaultHomeserverAddress = "matrix.org" + + // MARK: - Notifications + + var pusherAppId: String { + #if DEBUG + InfoPlistReader.target.baseBundleIdentifier + ".ios.dev" + #else + InfoPlistReader.target.baseBundleIdentifier + ".ios.prod" + #endif + } + + let pushGatewayBaseURL = URL(staticString: "https://matrix.org/_matrix/push/v1/notify") + + let enableNotifications = false + + // MARK: - Bug report + + let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports") + let bugReportSentryURL = URL(staticString: "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44") + // Use the name allocated by the bug report server + let bugReportApplicationId = "riot-ios" + let bugReportUISIId = "element-auto-uisi" + let bugReportGHLabels = ["Element-X"] + + // MARK: - Analytics + + #if DEBUG + /// The configuration to use for analytics during development. Set `isEnabled` to false to disable analytics in debug builds. + /// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations. + let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"), + host: "https://posthog.element.dev", + apiKey: "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", + termsURL: URL(staticString: "https://element.io/cookie-policy")) + #else + /// The configuration to use for analytics. Set `isEnabled` to false to disable analytics. + /// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations. + let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"), + host: "https://posthog.hss.element.io", + apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", + termsURL: URL(staticString: "https://element.io/cookie-policy")) + #endif + + /// Whether the user has already been shown the PostHog analytics prompt. + var hasSeenAnalyticsPrompt: Bool { + Self.store.object(forKey: UserDefaultsKeys.enableAnalytics.rawValue) != nil + } + + /// `true` when the user has opted in to send analytics. + @AppStorage(UserDefaultsKeys.enableAnalytics.rawValue, store: store) + var enableAnalytics = false + + /// Indicates if the device has already called identify for this session to PostHog. + /// This is separate to `enableAnalytics` as logging out leaves analytics + /// enabled, but requires the next account to be identified separately. + @AppStorage(UserDefaultsKeys.isIdentifiedForAnalytics.rawValue, store: store) + var isIdentifiedForAnalytics = false + + // MARK: - Room Screen + + @AppStorage(UserDefaultsKeys.timelineStyle.rawValue, store: store) + var timelineStyle = TimelineStyle.bubbles + + // MARK: - Client + + @AppStorage(UserDefaultsKeys.slidingSyncProxyBaseURLString.rawValue, store: store) + var slidingSyncProxyBaseURLString = "https://slidingsync.lab.element.dev" + + // MARK: - Notifications + + @AppStorage(UserDefaultsKeys.enableInAppNotifications.rawValue, store: store) + var enableInAppNotifications = true + + /// Tag describing which set of device specific rules a pusher executes. + @AppStorage(UserDefaultsKeys.pusherProfileTag.rawValue, store: store) + var pusherProfileTag: String? + + // MARK: - Other + + let permalinkBaseURL = URL(staticString: "https://matrix.to") +} diff --git a/ElementX/Sources/Application/BuildSettings.swift b/ElementX/Sources/Application/BuildSettings.swift deleted file mode 100644 index c2d483586..000000000 --- a/ElementX/Sources/Application/BuildSettings.swift +++ /dev/null @@ -1,80 +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 - -final class BuildSettings { - // MARK: - Bundle Settings - - static var pusherAppId: String { - #if DEBUG - InfoPlistReader.target.baseBundleIdentifier + ".ios.dev" - #else - InfoPlistReader.target.baseBundleIdentifier + ".ios.prod" - #endif - } - - // MARK: - Servers - - static let defaultHomeserverAddress = "matrix.org" - static let defaultSlidingSyncProxyBaseURLString = "https://slidingsync.lab.element.dev" - static let pushGatewayBaseURL = URL(staticString: "https://matrix.org/_matrix/push/v1/notify") - - // MARK: - Bug report - - static let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports") - static let bugReportSentryURL = URL(staticString: "https://f39ac49e97714316965b777d9f3d6cd8@sentry.tools.element.io/44") - // Use the name allocated by the bug report server - static let bugReportApplicationId = "riot-ios" - static let bugReportUISIId = "element-auto-uisi" - - static let bugReportGHLabels = ["Element-X"] - - // MARK: - Analytics - - #if DEBUG - /// The configuration to use for analytics during development. Set `isEnabled` to false to disable analytics in debug builds. - /// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations. - static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"), - host: "https://posthog.element.dev", - apiKey: "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", - termsURL: URL(staticString: "https://element.io/cookie-policy")) - #else - /// The configuration to use for analytics. Set `isEnabled` to false to disable analytics. - /// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations. - static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"), - host: "https://posthog.hss.element.io", - apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", - termsURL: URL(staticString: "https://element.io/cookie-policy")) - #endif - - // MARK: - Settings screen - - static let settingsCrashButtonVisible = true - static let settingsShowTimelineStyle = true - - // MARK: - Room screen - - static let defaultRoomTimelineStyle: TimelineStyle = .bubbles - - // MARK: - Other - - static var permalinkBaseURL = URL(staticString: "https://matrix.to") - - // MARK: - Notifications - - static let enableNotifications = false -} diff --git a/ElementX/Sources/Application/ServiceLocator.swift b/ElementX/Sources/Application/ServiceLocator.swift index d3dbf8f8b..e9e7f9605 100644 --- a/ElementX/Sources/Application/ServiceLocator.swift +++ b/ElementX/Sources/Application/ServiceLocator.swift @@ -26,4 +26,10 @@ class ServiceLocator { func register(userNotificationController: UserNotificationControllerProtocol) { self.userNotificationController = userNotificationController } + + private(set) var settings: AppSettings! + + func register(appSettings: AppSettings) { + settings = appSettings + } } diff --git a/ElementX/Sources/Other/ElementSettings.swift b/ElementX/Sources/Other/ElementSettings.swift deleted file mode 100644 index 802303cf1..000000000 --- a/ElementX/Sources/Other/ElementSettings.swift +++ /dev/null @@ -1,82 +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 - -/// Store Element specific app settings. -final class ElementSettings: ObservableObject { - // MARK: - Constants - - public enum UserDefaultsKeys: String { - case timelineStyle - case enableAnalytics - case isIdentifiedForAnalytics - case slidingSyncProxyBaseURLString - case enableInAppNotifications - case pusherProfileTag - } - - static let shared = ElementSettings() - - /// UserDefaults to be used on reads and writes. - static var store: UserDefaults { - guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.target.appGroupIdentifier) else { - fatalError("Fail to load shared UserDefaults") - } - return userDefaults - } - - private init() { - // no-op - } - - // MARK: - Analytics - - /// Whether the user has already been shown the PostHog analytics prompt. - var hasSeenAnalyticsPrompt: Bool { - Self.store.object(forKey: UserDefaultsKeys.enableAnalytics.rawValue) != nil - } - - /// `true` when the user has opted in to send analytics. - @AppStorage(UserDefaultsKeys.enableAnalytics.rawValue, store: store) - var enableAnalytics = false - - /// Indicates if the device has already called identify for this session to PostHog. - /// This is separate to `enableAnalytics` as logging out leaves analytics - /// enabled, but requires the next account to be identified separately. - @AppStorage(UserDefaultsKeys.isIdentifiedForAnalytics.rawValue, store: store) - var isIdentifiedForAnalytics = false - - // MARK: - Room Screen - - @AppStorage(UserDefaultsKeys.timelineStyle.rawValue, store: store) - var timelineStyle = BuildSettings.defaultRoomTimelineStyle - - // MARK: - Client - - @AppStorage(UserDefaultsKeys.slidingSyncProxyBaseURLString.rawValue, store: store) - var slidingSyncProxyBaseURLString = BuildSettings.defaultSlidingSyncProxyBaseURLString - - // MARK: - Notifications - - @AppStorage(UserDefaultsKeys.enableInAppNotifications.rawValue, store: store) - var enableInAppNotifications = true - - @AppStorage(UserDefaultsKeys.pusherProfileTag.rawValue, store: store) - /// Tag describing which set of device specific rules a pusher executes. - var pusherProfileTag: String? -} diff --git a/ElementX/Sources/Other/PermalinkBuilder.swift b/ElementX/Sources/Other/PermalinkBuilder.swift index 9691e944c..a8a1ce606 100644 --- a/ElementX/Sources/Other/PermalinkBuilder.swift +++ b/ElementX/Sources/Other/PermalinkBuilder.swift @@ -37,7 +37,7 @@ enum PermalinkBuilder { throw PermalinkBuilderError.invalidUserIdentifier } - let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(userIdentifier)" + let urlString = "\(ServiceLocator.shared.settings.permalinkBaseURL)/#/\(userIdentifier)" guard let url = URL(string: urlString) else { throw PermalinkBuilderError.failedConstructingURL @@ -75,7 +75,7 @@ enum PermalinkBuilder { throw PermalinkBuilderError.failedAddingPercentEncoding } - let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(roomId)/\(eventId)" + let urlString = "\(ServiceLocator.shared.settings.permalinkBaseURL)/#/\(roomId)/\(eventId)" guard let url = URL(string: urlString) else { throw PermalinkBuilderError.failedConstructingURL @@ -91,7 +91,7 @@ enum PermalinkBuilder { throw PermalinkBuilderError.failedAddingPercentEncoding } - let urlString = "\(BuildSettings.permalinkBaseURL)/#/\(identifier)" + let urlString = "\(ServiceLocator.shared.settings.permalinkBaseURL)/#/\(identifier)" guard let url = URL(string: urlString) else { throw PermalinkBuilderError.failedConstructingURL diff --git a/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift b/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift index db5e479a3..4f5db8103 100644 --- a/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift +++ b/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptCoordinator.swift @@ -30,7 +30,7 @@ final class AnalyticsPromptCoordinator: CoordinatorProtocol { init(parameters: AnalyticsPromptCoordinatorParameters) { self.parameters = parameters - viewModel = AnalyticsPromptViewModel(termsURL: BuildSettings.analyticsConfiguration.termsURL) + viewModel = AnalyticsPromptViewModel(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL) } // MARK: - Public diff --git a/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptModels.swift b/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptModels.swift index 17c5aad10..21d0b3440 100644 --- a/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptModels.swift +++ b/ElementX/Sources/Screens/AnalyticsPrompt/AnalyticsPromptModels.swift @@ -55,7 +55,7 @@ struct AnalyticsPromptStrings { // Replace the placeholder with a link. var link = AttributedString(ElementL10n.analyticsOptInContentLink) - link.link = BuildSettings.analyticsConfiguration.termsURL + link.link = ServiceLocator.shared.settings.analyticsConfiguration.termsURL optInContent.replaceSubrange(range, with: link) self.optInContent = optInContent diff --git a/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift b/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift index d8463b4a6..49d31a3e6 100644 --- a/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift +++ b/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift @@ -114,7 +114,7 @@ struct AnalyticsPrompt: View { // MARK: - Previews struct AnalyticsPrompt_Previews: PreviewProvider { - static let viewModel = AnalyticsPromptViewModel(termsURL: BuildSettings.analyticsConfiguration.termsURL) + static let viewModel = AnalyticsPromptViewModel(termsURL: ServiceLocator.shared.settings.analyticsConfiguration.termsURL) static var previews: some View { AnalyticsPrompt(context: viewModel.context) .tint(.element.accent) diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift index 82515131c..27e065146 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift @@ -61,7 +61,7 @@ class AuthenticationCoordinator: CoordinatorProtocol { private func startAuthentication() async { startLoading() - switch await authenticationService.configure(for: BuildSettings.defaultHomeserverAddress) { + switch await authenticationService.configure(for: ServiceLocator.shared.settings.defaultHomeserverAddress) { case .success: stopLoading() showLoginScreen() diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift index 5456cef47..04a15fed1 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift @@ -23,7 +23,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie init(homeserverAddress: String, isModallyPresented: Bool) { let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress, - slidingSyncProxyAddress: ElementSettings.shared.slidingSyncProxyBaseURLString) + slidingSyncProxyAddress: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString) super.init(initialViewState: ServerSelectionViewState(bindings: bindings, isModallyPresented: isModallyPresented)) @@ -33,8 +33,8 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie switch viewAction { case .confirm: if !state.bindings.slidingSyncProxyAddress.isEmpty, - state.bindings.slidingSyncProxyAddress != ElementSettings.shared.slidingSyncProxyBaseURLString { - ElementSettings.shared.slidingSyncProxyBaseURLString = state.bindings.slidingSyncProxyAddress + state.bindings.slidingSyncProxyAddress != ServiceLocator.shared.settings.slidingSyncProxyBaseURLString { + ServiceLocator.shared.settings.slidingSyncProxyBaseURLString = state.bindings.slidingSyncProxyAddress } callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress)) diff --git a/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift b/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift index e2e05d9b0..ae76cf640 100644 --- a/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift +++ b/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift @@ -68,7 +68,7 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol { let result = try await bugReportService.submitBugReport(text: context.reportText, includeLogs: context.sendingLogsEnabled, includeCrashLog: true, - githubLabels: BuildSettings.bugReportGHLabels, + githubLabels: ServiceLocator.shared.settings.bugReportGHLabels, files: files) MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)") callback?(.submitFinished) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index cfa127143..0d1dc37b0 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -17,7 +17,7 @@ import SwiftUI struct RoomScreen: View { - @ObservedObject private var settings = ElementSettings.shared + @ObservedObject private var settings = ServiceLocator.shared.settings @ObservedObject var context: RoomScreenViewModel.Context @State private var showReactionsMenuForItemId = "" diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift index 6dcde8c02..c194bfd78 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift @@ -35,7 +35,7 @@ enum TimelineStyle: String, CaseIterable { // MARK: - Environment private struct TimelineStyleKey: EnvironmentKey { - static let defaultValue = BuildSettings.defaultRoomTimelineStyle + static let defaultValue = TimelineStyle.bubbles } extension EnvironmentValues { diff --git a/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift index c9449a326..4a1d4791e 100644 --- a/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsCoordinator.swift @@ -50,8 +50,6 @@ final class SettingsCoordinator: CoordinatorProtocol { self.toggleAnalytics() case .reportBug: self.presentBugReportScreen() - case .crash: - self.parameters.bugReportService.crash() case .logout: self.callback?(.logout) } @@ -67,7 +65,7 @@ final class SettingsCoordinator: CoordinatorProtocol { // MARK: - Private private func toggleAnalytics() { - if ElementSettings.shared.enableAnalytics { + if ServiceLocator.shared.settings.enableAnalytics { Analytics.shared.optOut() } else { Analytics.shared.optIn(with: parameters.userSession) diff --git a/ElementX/Sources/Screens/Settings/SettingsModels.swift b/ElementX/Sources/Screens/Settings/SettingsModels.swift index 8b19a7c73..50962db31 100644 --- a/ElementX/Sources/Screens/Settings/SettingsModels.swift +++ b/ElementX/Sources/Screens/Settings/SettingsModels.swift @@ -21,7 +21,6 @@ enum SettingsViewModelAction { case close case toggleAnalytics case reportBug - case crash case logout } @@ -33,13 +32,12 @@ struct SettingsViewState: BindableState { } struct SettingsViewStateBindings { - var enableAnalytics = ElementSettings.shared.enableAnalytics + var enableAnalytics = ServiceLocator.shared.settings.enableAnalytics } enum SettingsViewAction { case close case toggleAnalytics case reportBug - case crash case logout } diff --git a/ElementX/Sources/Screens/Settings/SettingsViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsViewModel.swift index ac67bb087..bf534b34e 100644 --- a/ElementX/Sources/Screens/Settings/SettingsViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsViewModel.swift @@ -51,8 +51,6 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol { callback?(.toggleAnalytics) case .reportBug: callback?(.reportBug) - case .crash: - callback?(.crash) case .logout: callback?(.logout) } diff --git a/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift index 34033fc83..9704e5d7a 100644 --- a/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/View/SettingsScreen.swift @@ -19,7 +19,7 @@ import SwiftUI struct SettingsScreen: View { @State private var showingLogoutConfirmation = false @Environment(\.colorScheme) private var colorScheme - @ObservedObject private var settings = ElementSettings.shared + @ObservedObject private var settings = ServiceLocator.shared.settings @ScaledMetric private var avatarSize = AvatarSize.user(on: .settings).value @ScaledMetric private var menuIconSize = 30.0 @@ -110,30 +110,20 @@ struct SettingsScreen: View { .listRowSeparator(.hidden) .foregroundColor(.element.primaryContent) .accessibilityIdentifier("reportBugButton") - - if BuildSettings.settingsCrashButtonVisible { - Button("Crash app", - role: .destructive) { context.send(viewAction: .crash) - } - .listRowInsets(listRowInsets) - .accessibilityIdentifier("crashButton") - } } } @ViewBuilder private var userInterfaceSection: some View { - if BuildSettings.settingsShowTimelineStyle { - Section { - Picker(ElementL10n.settingsTimelineStyle, selection: $settings.timelineStyle) { - ForEach(TimelineStyle.allCases, id: \.self) { style in - Text(style.description) - .tag(style) - } + Section { + Picker(ElementL10n.settingsTimelineStyle, selection: $settings.timelineStyle) { + ForEach(TimelineStyle.allCases, id: \.self) { style in + Text(style.description) + .tag(style) } - .listRowInsets(listRowInsets) - .accessibilityIdentifier("timelineStylePicker") } + .listRowInsets(listRowInsets) + .accessibilityIdentifier("timelineStylePicker") } } diff --git a/ElementX/Sources/Services/Analytics/Analytics.swift b/ElementX/Sources/Services/Analytics/Analytics.swift index ad0ed461a..06e716ad2 100644 --- a/ElementX/Sources/Services/Analytics/Analytics.swift +++ b/ElementX/Sources/Services/Analytics/Analytics.swift @@ -49,14 +49,14 @@ 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. - !ElementSettings.shared.hasSeenAnalyticsPrompt && BuildSettings.analyticsConfiguration.isEnabled + !ServiceLocator.shared.settings.hasSeenAnalyticsPrompt && ServiceLocator.shared.settings.analyticsConfiguration.isEnabled } /// Opts in to analytics tracking with the supplied user session. /// - Parameter userSession: The user session to use to when reading/generating the analytics ID. /// The session will be ignored if not running. func optIn(with userSession: UserSessionProtocol) { - ElementSettings.shared.enableAnalytics = true + ServiceLocator.shared.settings.enableAnalytics = true startIfEnabled() Task { await useAnalyticsSettings(from: userSession) } @@ -64,7 +64,7 @@ class Analytics { /// Stops analytics tracking and calls `reset` to clear any IDs and event queues. func optOut() { - ElementSettings.shared.enableAnalytics = false + ServiceLocator.shared.settings.enableAnalytics = false // The order is important here. PostHog ignores the reset if stopped. reset() @@ -76,7 +76,7 @@ class Analytics { /// Starts the analytics client if the user has opted in, otherwise does nothing. func startIfEnabled() { - guard ElementSettings.shared.enableAnalytics, !isRunning else { return } + guard ServiceLocator.shared.settings.enableAnalytics, !isRunning else { return } client.start() // monitoringClient.start() @@ -96,8 +96,8 @@ class Analytics { /// - Parameter userSession: The user session to read analytics settings from. func useAnalyticsSettings(from userSession: UserSessionProtocol) async { guard - ElementSettings.shared.enableAnalytics, - !ElementSettings.shared.isIdentifiedForAnalytics + ServiceLocator.shared.settings.enableAnalytics, + !ServiceLocator.shared.settings.isIdentifiedForAnalytics else { return } let service = AnalyticsService(userSession: userSession) @@ -122,7 +122,7 @@ class Analytics { // monitoringClient.reset() MXLog.debug("Reset.") - ElementSettings.shared.isIdentifiedForAnalytics = false + ServiceLocator.shared.settings.isIdentifiedForAnalytics = false // Stop collecting crash logs // MXLogger.logCrashes(false) @@ -147,7 +147,7 @@ class Analytics { client.identify(id: id) MXLog.debug("Identified.") - ElementSettings.shared.isIdentifiedForAnalytics = true + ServiceLocator.shared.settings.isIdentifiedForAnalytics = true } /// Capture an event in the `client`. diff --git a/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift b/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift index e1f4ed442..4da877e6c 100644 --- a/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift +++ b/ElementX/Sources/Services/Analytics/PHGPostHogConfiguration.swift @@ -18,7 +18,7 @@ import PostHog extension PHGPostHogConfiguration { static var standard: PHGPostHogConfiguration? { - let analyticsConfiguration = BuildSettings.analyticsConfiguration + let analyticsConfiguration = ServiceLocator.shared.settings.analyticsConfiguration guard analyticsConfiguration.isEnabled else { return nil } let postHogConfiguration = PHGPostHogConfiguration(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host) diff --git a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift index 673295701..ce642c7b4 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift @@ -22,7 +22,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { private let authenticationService: AuthenticationService private let userSessionStore: UserSessionStoreProtocol - private(set) var homeserver = LoginHomeserver(address: BuildSettings.defaultHomeserverAddress, loginMode: .unknown) + private(set) var homeserver = LoginHomeserver(address: ServiceLocator.shared.settings.defaultHomeserverAddress, loginMode: .unknown) init(userSessionStore: UserSessionStoreProtocol) { self.userSessionStore = userSessionStore diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index 2c098c4f3..2f0a81c5f 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -28,7 +28,7 @@ class BugReportService: BugReportServiceProtocol { init(withBaseURL baseURL: URL, sentryURL: URL, - applicationId: String = BuildSettings.bugReportApplicationId, + applicationId: String = ServiceLocator.shared.settings.bugReportApplicationId, 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 baee55bab..6ce80c899 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -81,7 +81,7 @@ class ClientProxy: ClientProxyProtocol { await Task.dispatch(on: clientQueue) { do { - let slidingSyncBuilder = try client.slidingSync().homeserver(url: ElementSettings.shared.slidingSyncProxyBaseURLString) + let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString) let slidingSyncView = try SlidingSyncViewBuilder() .timelineLimit(limit: 10) diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift index 668117b08..3378124f2 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift @@ -89,12 +89,12 @@ class NotificationManager: NSObject, NotificationManagerProtocol { do { try await clientProxy.setPusher(pushkey: deviceToken.base64EncodedString(), kind: .http, - appId: BuildSettings.pusherAppId, + appId: ServiceLocator.shared.settings.pusherAppId, appDisplayName: "\(InfoPlistReader.target.bundleDisplayName) (iOS)", deviceDisplayName: UIDevice.current.name, profileTag: pusherProfileTag(), lang: Bundle.preferredLanguages.first ?? "en", - url: BuildSettings.pushGatewayBaseURL.absoluteString, + url: ServiceLocator.shared.settings.pushGatewayBaseURL.absoluteString, format: .eventIdOnly, defaultPayload: [ "aps": [ @@ -113,7 +113,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { } private func pusherProfileTag() -> String { - if let currentTag = ElementSettings.shared.pusherProfileTag { + if let currentTag = ServiceLocator.shared.settings.pusherProfileTag { return currentTag } let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -122,7 +122,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { return String(chars[chars.index(chars.startIndex, offsetBy: offset)]) }.joined() - ElementSettings.shared.pusherProfileTag = newTag + ServiceLocator.shared.settings.pusherProfileTag = newTag return newTag } } @@ -132,7 +132,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { extension NotificationManager: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { - guard ElementSettings.shared.enableInAppNotifications else { + guard ServiceLocator.shared.settings.enableInAppNotifications else { return [] } guard let delegate else { diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 3f9f3d7b7..229ef1d37 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -36,6 +36,12 @@ class UserSessionStore: UserSessionStoreProtocol { MXLog.debug("Setup base directory at: \(baseDirectory)") } + /// Deletes all data stored in the shared container and keychain + func reset() { + try? FileManager.default.removeItem(at: baseDirectory) + keychainController.removeAllRestorationTokens() + } + func restoreUserSession() async -> Result { let availableCredentials = keychainController.restorationTokens() diff --git a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift index c402ee2fb..8ea1ae2ea 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift @@ -25,6 +25,9 @@ enum UserSessionStoreError: Error { } protocol UserSessionStoreProtocol { + /// Deletes all data stored in the shared container and keychain + func reset() + /// Whether or not there are sessions in the store. var hasSessions: Bool { get } diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 716d033a5..c28481bd8 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -28,6 +28,10 @@ class UITestsAppCoordinator: AppCoordinatorProtocol { mockScreens = UITestScreenIdentifier.allCases.map { MockScreen(id: $0, navigationRootCoordinator: navigationRootCoordinator) } ServiceLocator.shared.register(userNotificationController: MockUserNotificationController()) + + AppSettings.configureWithSuiteName("io.element.elementx.uitests") + AppSettings.reset() + ServiceLocator.shared.register(appSettings: AppSettings()) } func start() { diff --git a/UITests/Sources/SettingsUITests.swift b/UITests/Sources/SettingsUITests.swift index 40050478a..7743dcb38 100644 --- a/UITests/Sources/SettingsUITests.swift +++ b/UITests/Sources/SettingsUITests.swift @@ -26,12 +26,9 @@ class SettingsUITests: XCTestCase { let reportBugButton = app.buttons["reportBugButton"] XCTAssert(reportBugButton.exists) XCTAssertEqual(reportBugButton.label, ElementL10n.sendBugReport) - XCTAssertEqual(app.buttons["crashButton"].exists, BuildSettings.settingsCrashButtonVisible) - XCTAssertEqual(app.buttons["timelineStylePicker"].exists, BuildSettings.settingsShowTimelineStyle) - if BuildSettings.settingsShowTimelineStyle { - XCTAssertTrue(app.staticTexts[ElementL10n.settingsTimelineStyle].exists) - } + XCTAssertTrue(app.buttons["timelineStylePicker"].exists) + XCTAssertTrue(app.staticTexts[ElementL10n.settingsTimelineStyle].exists) let logoutButton = app.buttons["logoutButton"] XCTAssert(logoutButton.exists) diff --git a/UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.settings.png b/UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.settings.png index b3bbd3f04..3182f305f 100644 --- a/UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.settings.png +++ b/UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27d058762e6eafc940c71b8e4edeef26f6ccad34e086ad6a0f0a30252848485f -size 93290 +oid sha256:0ab7f75524ce702d1d0344884aad7bafa821b7cdd8215c28aff7ddcf0f5d184e +size 90671 diff --git a/UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.settings.png b/UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.settings.png index ba6a3cf68..2f75413e3 100644 --- a/UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.settings.png +++ b/UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97980472cdf36a0d07109fc066bd8184013b0a99cbbd164e82dbfb0a20cbc09f -size 115670 +oid sha256:79163533dc0069a51c0309ad4cc5291a324457949d69155954fa2c8529de9478 +size 109653 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.settings.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.settings.png index 1de039054..3b77df100 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.settings.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbbb771047c24948ad5b38308273fc1262667bdaafcf0bac61704b3186f66f1d -size 93992 +oid sha256:32ddcc37817a668eb87e0670e6b0fc35de66df19e0e12904c10532f0bc4172c5 +size 91443 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.settings.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.settings.png index 9f851def0..cdc8d5493 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.settings.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:063feeb819831472533b47c11eb8a69924843fea616ef3f56651274fd8f970c9 -size 115397 +oid sha256:5e30413bbd9e9268085b4564ab646f25a69a262285d68ec2477eba7dd68c3653 +size 111343 diff --git a/UITests/Sources/__Snapshots__/Application/fr-FR-iPad-9th-generation.settings.png b/UITests/Sources/__Snapshots__/Application/fr-FR-iPad-9th-generation.settings.png index de9889de9..bdbcdf26d 100644 --- a/UITests/Sources/__Snapshots__/Application/fr-FR-iPad-9th-generation.settings.png +++ b/UITests/Sources/__Snapshots__/Application/fr-FR-iPad-9th-generation.settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df1a88376fa6e083729329fcf221093c4704d900de8acc40600a61b7fd812a2c -size 94430 +oid sha256:9cbc095e0d4f194b01ac4b560315b8914100302ad33e8d7a2d2c81ea6280ecb7 +size 91866 diff --git a/UITests/Sources/__Snapshots__/Application/fr-FR-iPhone-14.settings.png b/UITests/Sources/__Snapshots__/Application/fr-FR-iPhone-14.settings.png index bc2466729..9f66e4ad7 100644 --- a/UITests/Sources/__Snapshots__/Application/fr-FR-iPhone-14.settings.png +++ b/UITests/Sources/__Snapshots__/Application/fr-FR-iPhone-14.settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4b3f25e56c50b01e3ce87c4bd0537e752664e23b7b761a5bdb1e2b88ed4dc35 -size 117371 +oid sha256:a44a61f155c7d0559982a70fa1ffd1a84828e8a8a9f9998ea6cb05210f4e8798 +size 113408 diff --git a/UITests/SupportingFiles/target.yml b/UITests/SupportingFiles/target.yml index 55a0be520..9a1873946 100644 --- a/UITests/SupportingFiles/target.yml +++ b/UITests/SupportingFiles/target.yml @@ -57,7 +57,7 @@ targets: - "**/__Snapshots__/**" - path: ../SupportingFiles - path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/UI - - path: ../../ElementX/Sources/Application/BuildSettings.swift + - path: ../../ElementX/Sources/Application/AppSettings.swift - path: ../../ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift - path: ../../ElementX/Sources/Services/Analytics/AnalyticsConfiguration.swift - path: ../../ElementX/Sources/UITests/UITestScreenIdentifier.swift diff --git a/UnitTests/Sources/AnalyticsTests.swift b/UnitTests/Sources/AnalyticsTests.swift index 9b8331438..ae7ffa558 100644 --- a/UnitTests/Sources/AnalyticsTests.swift +++ b/UnitTests/Sources/AnalyticsTests.swift @@ -19,10 +19,16 @@ import AnalyticsEvents import XCTest class AnalyticsTests: XCTestCase { + private var applicationSettings: AppSettings! + + override func setUp() { + AppSettings.configureWithSuiteName("io.element.elementx.unitests") + AppSettings.reset() + applicationSettings = AppSettings() + } + func testAnalyticsPromptNewUser() { // Given a fresh install of the app (without PostHog analytics having been set). - ElementSettings.store.removeObject(forKey: ElementSettings.UserDefaultsKeys.enableAnalytics.rawValue) - // When the user is prompted for analytics. let showPrompt = Analytics.shared.shouldShowAnalyticsPrompt @@ -32,7 +38,7 @@ class AnalyticsTests: XCTestCase { func testAnalyticsPromptUserDeclinedPostHog() { // Given an existing install of the app where the user previously declined PostHog - ElementSettings.shared.enableAnalytics = false + applicationSettings.enableAnalytics = false // When the user is prompted for analytics let showPrompt = Analytics.shared.shouldShowAnalyticsPrompt @@ -43,7 +49,7 @@ class AnalyticsTests: XCTestCase { func testAnalyticsPromptUserAcceptedPostHog() { // Given an existing install of the app where the user previously accepted PostHog - ElementSettings.shared.enableAnalytics = true + applicationSettings.enableAnalytics = true // When the user is prompted for analytics let showPrompt = Analytics.shared.shouldShowAnalyticsPrompt diff --git a/UnitTests/Sources/PermalinkBuilderTests.swift b/UnitTests/Sources/PermalinkBuilderTests.swift index ac97a86df..d7145b593 100644 --- a/UnitTests/Sources/PermalinkBuilderTests.swift +++ b/UnitTests/Sources/PermalinkBuilderTests.swift @@ -18,12 +18,20 @@ import XCTest class PermalinkBuilderTests: XCTestCase { + private var appSettings: AppSettings! + + override func setUp() { + AppSettings.configureWithSuiteName("io.element.elementx.unitests") + AppSettings.reset() + appSettings = AppSettings() + } + func testUserIdentifierPermalink() { let userId = "@abcdefghijklmnopqrstuvwxyz1234567890._-=/:matrix.org" do { let permalink = try PermalinkBuilder.permalinkTo(userIdentifier: userId) - XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/\(userId)")) + XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/\(userId)")) } catch { XCTFail("User identifier must be valid: \(error)") } @@ -43,7 +51,7 @@ class PermalinkBuilderTests: XCTestCase { do { let permalink = try PermalinkBuilder.permalinkTo(roomIdentifier: roomId) - XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org")) + XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org")) } catch { XCTFail("Room identifier must be valid: \(error)") } @@ -63,7 +71,7 @@ class PermalinkBuilderTests: XCTestCase { do { let permalink = try PermalinkBuilder.permalinkTo(roomAlias: roomAlias) - XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/%23abcdefghijklmnopqrstuvwxyz-_.1234567890%3Amatrix.org")) + XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/%23abcdefghijklmnopqrstuvwxyz-_.1234567890%3Amatrix.org")) } catch { XCTFail("Room alias must be valid: \(error)") } @@ -84,7 +92,7 @@ class PermalinkBuilderTests: XCTestCase { do { let permalink = try PermalinkBuilder.permalinkTo(eventIdentifier: eventId, roomIdentifier: roomId) - XCTAssertEqual(permalink, URL(string: "\(BuildSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org/%24abcdefghijklmnopqrstuvwxyz1234567890")) + XCTAssertEqual(permalink, URL(string: "\(appSettings.permalinkBaseURL)/#/!abcdefghijklmnopqrstuvwxyz1234567890%3Amatrix.org/%24abcdefghijklmnopqrstuvwxyz1234567890")) } catch { XCTFail("Room and event identifiers must be valid: \(error)") } diff --git a/UnitTests/Sources/SettingsViewModelTests.swift b/UnitTests/Sources/SettingsViewModelTests.swift index 0ce792c22..6460e5f81 100644 --- a/UnitTests/Sources/SettingsViewModelTests.swift +++ b/UnitTests/Sources/SettingsViewModelTests.swift @@ -56,15 +56,4 @@ class SettingsViewModelTests: XCTestCase { await Task.yield() XCTAssert(correctResult) } - - func testCrash() async throws { - var correctResult = false - viewModel.callback = { result in - correctResult = result == .crash - } - - context.send(viewAction: .crash) - await Task.yield() - XCTAssert(correctResult) - } }