mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -26,4 +26,10 @@ class ServiceLocator {
|
||||
func register(userNotificationController: UserNotificationControllerProtocol) {
|
||||
self.userNotificationController = userNotificationController
|
||||
}
|
||||
|
||||
private(set) var settings: AppSettings!
|
||||
|
||||
func register(appSettings: AppSettings) {
|
||||
settings = appSettings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
+3
-3
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol {
|
||||
callback?(.toggleAnalytics)
|
||||
case .reportBug:
|
||||
callback?(.reportBug)
|
||||
case .crash:
|
||||
callback?(.crash)
|
||||
case .logout:
|
||||
callback?(.logout)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<UserSession, UserSessionStoreError> {
|
||||
let availableCredentials = keychainController.restorationTokens()
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user