mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
AppSettings are Mockable (#5589)
* create DependenciesProtocol and replace ServiceLocator registration with it replace mocking calls for ServiceLocator to Dependencies replace ServiceLocator with instance level calls replace ServiceLocator configs with TestDependencies config in Tests replace servicelocator usage in analytics service replace remaining ServiceLocator calls in unit tests that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances fix swiftlint warning remove remaining references to ServiceLocator make dependencies properties consistent in tests delete Dependencies and DependenciesProtocol fix whitespace modifications updates from PR feedback a pass over converting AppSettings and AnalyticsService test instance properties to per test instantiation fix a missed PR feedback Sync up after rebase create DependenciesProtocol and replace ServiceLocator registration with it replace mocking calls for ServiceLocator to Dependencies replace ServiceLocator with instance level calls replace ServiceLocator configs with TestDependencies config in Tests replace servicelocator usage in analytics service replace remaining ServiceLocator calls in unit tests that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances fix swiftlint warning remove remaining references to ServiceLocator make dependencies properties consistent in tests delete Dependencies and DependenciesProtocol * AppSettings is instance level mockable now * replace mocked and preview usages of reset + AppSettings global init with AppSettings(store: UserDefaultsMock()) * AppSettings.UserDefaultsKeys is now a RawRep struct, making it extensible * update tests with new AppSettings so it builds * update remaining AppSettings test/mock inits * AppSettings.UserDefaultsKey conformed to ExpressibleByStringLiteral * addressed issues found in first pass ai review * update AppSettings instantiations after rebase * fix accessibility tests coordinator after rebase * Rename to VolatileUserDefaults * AppSettings init uses mandatory parameter * Revert UserDefaultsKey to enum * Add TrackedUserDefaults to be able to reset the defaults object * Added volatile static func to AppSettings * Use .volatile() static func instead of full init * review fixups * Resolve build issues after rebase * updated enterprise module * remove the static reset settings call from the preview tests --------- Co-authored-by: Mauro Romito <mauro.romito@element.io>
This commit is contained in:
@@ -37,7 +37,7 @@ enum AppBuildType {
|
||||
///
|
||||
/// State is persisted in `UserDefaults`, which is thread-safe per Apple's documentation, hence `@unchecked`.
|
||||
final class AppSettings: @unchecked Sendable {
|
||||
private enum UserDefaultsKeys: String {
|
||||
fileprivate enum UserDefaultsKeys: String, PreferenceKeyable {
|
||||
case lastVersionLaunched
|
||||
case seenInvites
|
||||
case hasSeenNewSoundBanner
|
||||
@@ -94,10 +94,10 @@ final class AppSettings: @unchecked Sendable {
|
||||
case developerOptionsEnabled
|
||||
}
|
||||
|
||||
private nonisolated(unsafe) static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||
static let suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||
|
||||
/// UserDefaults to be used on reads and writes. `UserDefaults` is thread-safe per Apple's documentation.
|
||||
private nonisolated(unsafe) static var store: UserDefaults! = UserDefaults(suiteName: suiteName)
|
||||
/// UserDefaults to be used on reads and writes.
|
||||
private let store: UserDefaultsProtocol
|
||||
|
||||
static var appBuildType: AppBuildType {
|
||||
#if DEBUG
|
||||
@@ -112,26 +112,16 @@ final class AppSettings: @unchecked Sendable {
|
||||
#endif
|
||||
}
|
||||
|
||||
static func resetAllSettings() {
|
||||
func resetAllSettings() {
|
||||
MXLog.warning("Resetting the AppSettings.")
|
||||
store.removePersistentDomain(forName: suiteName)
|
||||
store.reset()
|
||||
}
|
||||
|
||||
static func resetSessionSpecificSettings() {
|
||||
func resetSessionSpecificSettings() {
|
||||
MXLog.warning("Resetting the user session specific AppSettings.")
|
||||
store.removeObject(forKey: UserDefaultsKeys.hasRunIdentityConfirmationOnboarding.rawValue)
|
||||
}
|
||||
|
||||
static func configureWithSuiteName(_ name: String) {
|
||||
suiteName = name
|
||||
|
||||
guard let userDefaults = UserDefaults(suiteName: name) else {
|
||||
fatalError("Fail to load shared UserDefaults")
|
||||
}
|
||||
|
||||
store = userDefaults
|
||||
}
|
||||
|
||||
// MARK: - Hooks
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
@@ -182,17 +172,17 @@ final class AppSettings: @unchecked Sendable {
|
||||
/// The last known version of the app that was launched on this device, which is
|
||||
/// used to detect when migrations should be run. When `nil` the app may have been
|
||||
/// deleted between runs so should clear data in the shared container and keychain.
|
||||
@UserPreference(key: UserDefaultsKeys.lastVersionLaunched, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var lastVersionLaunched: String?
|
||||
|
||||
/// The Set of room identifiers of invites that the user already saw in the invites list.
|
||||
/// This Set is being used to implement badges for unread invites.
|
||||
@UserPreference(key: UserDefaultsKeys.seenInvites, defaultValue: [], storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var seenInvites: Set<String>
|
||||
|
||||
/// Defaults to `true` for new users, and we use a migration to set it to `false` for existing users.
|
||||
@UserPreference(key: UserDefaultsKeys.hasSeenNewSoundBanner, defaultValue: true, storageType: .userDefaults(store))
|
||||
var hasSeenNewSoundBanner
|
||||
@UserPreference
|
||||
var hasSeenNewSoundBanner: Bool
|
||||
|
||||
/// The initial set of account providers shown to the user in the authentication flow.
|
||||
///
|
||||
@@ -236,7 +226,7 @@ final class AppSettings: @unchecked Sendable {
|
||||
/// **Note:** This property isn't overridable as it in unexpected for forks to come across the error (or to even have a "Pro" app).
|
||||
let elementProAppStoreURL: URL = "https://apps.apple.com/app/element-pro-for-work/id6502951615"
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.appAppearance, defaultValue: .system, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var appAppearance: AppAppearance
|
||||
|
||||
// MARK: - Security
|
||||
@@ -248,7 +238,7 @@ final class AppSettings: @unchecked Sendable {
|
||||
/// Any codes that the user isn't allowed to use for their PIN.
|
||||
let appLockPINCodeBlockList = ["0000", "1234"]
|
||||
/// The number of attempts the user has made to unlock the app with a PIN code (resets when unlocked).
|
||||
@UserPreference(key: UserDefaultsKeys.appLockNumberOfPINAttempts, defaultValue: 0, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var appLockNumberOfPINAttempts: Int
|
||||
|
||||
// MARK: - Authentication
|
||||
@@ -287,33 +277,33 @@ final class AppSettings: @unchecked Sendable {
|
||||
pushGatewayBaseURL.appending(path: "_matrix/push/v1/notify")
|
||||
}
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store))
|
||||
var enableNotifications
|
||||
@UserPreference
|
||||
var enableNotifications: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store))
|
||||
var enableInAppNotifications
|
||||
@UserPreference
|
||||
var enableInAppNotifications: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.hideQuietNotificationAlerts, defaultValue: false, storageType: .userDefaults(store))
|
||||
var hideQuietNotificationAlerts
|
||||
@UserPreference
|
||||
var hideQuietNotificationAlerts: Bool
|
||||
|
||||
/// Tag describing which set of device specific rules a pusher executes.
|
||||
@UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var pusherProfileTag: String?
|
||||
|
||||
/// The device's last boot time as recorded by the NSE.
|
||||
@UserPreference(key: UserDefaultsKeys.lastNotificationBootTime, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var lastNotificationBootTime: TimeInterval?
|
||||
|
||||
/// The sound played when delivering noisy notifications. If nil, use the ElementX default
|
||||
@UserPreference(key: UserDefaultsKeys.selectedNotificationTone, defaultValue: nil, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var selectedNotificationTone: NotificationTone?
|
||||
|
||||
// MARK: - Logging
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.logLevel, defaultValue: LogLevel.info, storageType: .userDefaults(store))
|
||||
var logLevel
|
||||
@UserPreference
|
||||
var logLevel: LogLevel
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.traceLogPacks, defaultValue: [], storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var traceLogPacks: Set<TraceLogPack>
|
||||
|
||||
// MARK: - Bug report
|
||||
@@ -341,46 +331,46 @@ final class AppSettings: @unchecked Sendable {
|
||||
}
|
||||
|
||||
/// Whether the user has opted in to send analytics.
|
||||
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
|
||||
var analyticsConsentState
|
||||
@UserPreference
|
||||
var analyticsConsentState: AnalyticsConsentState
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.hasRunNotificationPermissionsOnboarding, defaultValue: false, storageType: .userDefaults(store))
|
||||
var hasRunNotificationPermissionsOnboarding
|
||||
@UserPreference
|
||||
var hasRunNotificationPermissionsOnboarding: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.hasRunIdentityConfirmationOnboarding, defaultValue: false, storageType: .userDefaults(store))
|
||||
var hasRunIdentityConfirmationOnboarding
|
||||
@UserPreference
|
||||
var hasRunIdentityConfirmationOnboarding: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.hasRequestedLocationAlwaysLocationAuthorization, defaultValue: false, storageType: .userDefaults(store))
|
||||
var hasRequestedLocationAlwaysLocationAuthorization
|
||||
@UserPreference
|
||||
var hasRequestedLocationAlwaysLocationAuthorization: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.frequentlyUsedSystemEmojis, defaultValue: [FrequentlyUsedEmoji](), storageType: .userDefaults(store))
|
||||
var frequentlyUsedSystemEmojis
|
||||
@UserPreference
|
||||
var frequentlyUsedSystemEmojis: [FrequentlyUsedEmoji]
|
||||
|
||||
// MARK: - Live Location
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.liveLocationSharingTimeoutDatesByRoomID, defaultValue: [String: LiveLocationSession](), storageType: .userDefaults(store))
|
||||
var liveLocationSharingSessionsByRoomID
|
||||
@UserPreference
|
||||
var liveLocationSharingSessionsByRoomID: [String: LiveLocationSession]
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.liveLocationMinimumDistanceUpdate, defaultValue: 10, storageType: .userDefaults(store))
|
||||
var liveLocationMinimumDistanceUpdate
|
||||
@UserPreference
|
||||
var liveLocationMinimumDistanceUpdate: Int
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.liveLocationDisclaimerDisplayed, defaultValue: false, storageType: .userDefaults(store))
|
||||
var liveLocationDisclaimerDisplayed
|
||||
@UserPreference
|
||||
var liveLocationDisclaimerDisplayed: Bool
|
||||
|
||||
// MARK: - Home Screen
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.roomListActivityVisibility, defaultValue: .current, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var roomListActivityVisibility: RoomListActivityVisibility
|
||||
|
||||
// MARK: - Room Screen
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.viewSourceEnabled, defaultValue: appBuildType == .debug, storageType: .userDefaults(store))
|
||||
var viewSourceEnabled
|
||||
@UserPreference
|
||||
var viewSourceEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.optimizeMediaUploads, defaultValue: true, storageType: .userDefaults(store))
|
||||
var optimizeMediaUploads
|
||||
@UserPreference
|
||||
var optimizeMediaUploads: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.voiceMessagePlaybackSpeed, defaultValue: AudioPlaybackSpeed.default, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var voiceMessagePlaybackSpeed: AudioPlaybackSpeed
|
||||
|
||||
/// Whether or not to show a warning on the media caption composer so the user knows
|
||||
@@ -399,7 +389,7 @@ final class AppSettings: @unchecked Sendable {
|
||||
let elementCallPosthogAPIKey = "phc_rXGHx9vDmyEvyRxPziYtdVIv0ahEv8A9uLWFcCi1WcU"
|
||||
let elementCallPosthogSentryDSN = "https://3bd2f95ba5554d4497da7153b552ffb5@sentry.tools.element.io/41"
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.elementCallBaseURLOverride, defaultValue: nil, storageType: .userDefaults(store))
|
||||
@UserPreference
|
||||
var elementCallBaseURLOverride: URL?
|
||||
|
||||
// MARK: - Users
|
||||
@@ -417,51 +407,111 @@ final class AppSettings: @unchecked Sendable {
|
||||
|
||||
// MARK: - Presence
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.sharePresence, defaultValue: true, storageType: .userDefaults(store))
|
||||
var sharePresence
|
||||
@UserPreference
|
||||
var sharePresence: Bool
|
||||
|
||||
// MARK: - Feature Flags
|
||||
|
||||
/// Others
|
||||
@UserPreference(key: UserDefaultsKeys.fuzzyRoomListSearchEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var fuzzyRoomListSearchEnabled
|
||||
@UserPreference
|
||||
var fuzzyRoomListSearchEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.lowPriorityFilterEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var lowPriorityFilterEnabled
|
||||
@UserPreference
|
||||
var lowPriorityFilterEnabled: Bool
|
||||
|
||||
/// Configuration to enable only signed device isolation mode for crypto. In this mode only devices signed by their owner will be considered in e2ee rooms.
|
||||
@UserPreference(key: UserDefaultsKeys.enableOnlySignedDeviceIsolationMode, defaultValue: false, storageType: .userDefaults(store))
|
||||
var enableOnlySignedDeviceIsolationMode
|
||||
@UserPreference
|
||||
var enableOnlySignedDeviceIsolationMode: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var knockingEnabled
|
||||
@UserPreference
|
||||
var knockingEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.threadsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var threadsEnabled
|
||||
@UserPreference
|
||||
var threadsEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.roomThreadListEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var roomThreadListEnabled
|
||||
@UserPreference
|
||||
var roomThreadListEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.focusEventOnNotificationTap, defaultValue: false, storageType: .userDefaults(store))
|
||||
var focusEventOnNotificationTap
|
||||
@UserPreference
|
||||
var focusEventOnNotificationTap: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.linkPreviewsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var linkPreviewsEnabled
|
||||
@UserPreference
|
||||
var linkPreviewsEnabled: Bool
|
||||
|
||||
@UserPreference
|
||||
var jumpToReadMarkerEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.jumpToReadMarkerEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var jumpToReadMarkerEnabled
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.linkNewDeviceEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var linkNewDeviceEnabled
|
||||
@UserPreference
|
||||
var linkNewDeviceEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.automaticBackPaginationEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var automaticBackPaginationEnabled
|
||||
@UserPreference
|
||||
var automaticBackPaginationEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.clientPausingAndResumingEnabled, defaultValue: appBuildType != .release, storageType: .volatile)
|
||||
var clientPausingAndResumingEnabled
|
||||
@UserPreference
|
||||
var clientPausingAndResumingEnabled: Bool
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: appBuildType != .release, storageType: .userDefaults(store))
|
||||
var developerOptionsEnabled
|
||||
@UserPreference
|
||||
var developerOptionsEnabled: Bool
|
||||
|
||||
init(store: UserDefaultsProtocol) {
|
||||
// UserDefaults to be used on reads and writes.
|
||||
self.store = store
|
||||
|
||||
_lastVersionLaunched = UserPreference(key: .lastVersionLaunched, storage: store)
|
||||
_seenInvites = UserPreference(key: .seenInvites, defaultValue: [], storage: store)
|
||||
_hasSeenNewSoundBanner = UserPreference(key: .hasSeenNewSoundBanner, defaultValue: true, storage: store)
|
||||
_appAppearance = UserPreference(key: .appAppearance, defaultValue: .system, storage: store)
|
||||
_appLockNumberOfPINAttempts = UserPreference(key: .appLockNumberOfPINAttempts, defaultValue: 0, storage: store)
|
||||
_enableNotifications = UserPreference(key: .enableNotifications, defaultValue: true, storage: store)
|
||||
_enableInAppNotifications = UserPreference(key: .enableInAppNotifications, defaultValue: true, storage: store)
|
||||
_hideQuietNotificationAlerts = UserPreference(key: .hideQuietNotificationAlerts, defaultValue: false, storage: store)
|
||||
_pusherProfileTag = UserPreference(key: .pusherProfileTag, storage: store)
|
||||
_lastNotificationBootTime = UserPreference(key: .lastNotificationBootTime, storage: store)
|
||||
_selectedNotificationTone = UserPreference(key: .selectedNotificationTone, storage: store)
|
||||
_logLevel = UserPreference(key: .logLevel, defaultValue: LogLevel.info, storage: store)
|
||||
_traceLogPacks = UserPreference(key: .traceLogPacks, defaultValue: [], storage: store)
|
||||
_analyticsConsentState = UserPreference(key: .analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storage: store)
|
||||
_hasRunNotificationPermissionsOnboarding = UserPreference(key: .hasRunNotificationPermissionsOnboarding, defaultValue: false, storage: store)
|
||||
_hasRunIdentityConfirmationOnboarding = UserPreference(key: .hasRunIdentityConfirmationOnboarding, defaultValue: false, storage: store)
|
||||
_hasRequestedLocationAlwaysLocationAuthorization = UserPreference(key: .hasRequestedLocationAlwaysLocationAuthorization, defaultValue: false, storage: store)
|
||||
_frequentlyUsedSystemEmojis = UserPreference(key: .frequentlyUsedSystemEmojis, defaultValue: [FrequentlyUsedEmoji](), storage: store)
|
||||
_liveLocationSharingSessionsByRoomID = UserPreference(key: .liveLocationSharingTimeoutDatesByRoomID, defaultValue: [String: LiveLocationSession](), storage: store)
|
||||
_liveLocationMinimumDistanceUpdate = UserPreference(key: .liveLocationMinimumDistanceUpdate, defaultValue: 10, storage: store)
|
||||
_liveLocationDisclaimerDisplayed = UserPreference(key: .liveLocationDisclaimerDisplayed, defaultValue: false, storage: store)
|
||||
_roomListActivityVisibility = UserPreference(key: .roomListActivityVisibility, defaultValue: .current, storage: store)
|
||||
_viewSourceEnabled = UserPreference(key: .viewSourceEnabled, defaultValue: Self.appBuildType == .debug, storage: store)
|
||||
_optimizeMediaUploads = UserPreference(key: .optimizeMediaUploads, defaultValue: true, storage: store)
|
||||
_voiceMessagePlaybackSpeed = UserPreference(key: .voiceMessagePlaybackSpeed, defaultValue: AudioPlaybackSpeed.default, storage: store)
|
||||
_elementCallBaseURLOverride = UserPreference(key: .elementCallBaseURLOverride, defaultValue: nil, storage: store)
|
||||
_sharePresence = UserPreference(key: .sharePresence, defaultValue: true, storage: store)
|
||||
_fuzzyRoomListSearchEnabled = UserPreference(key: .fuzzyRoomListSearchEnabled, defaultValue: false, storage: store)
|
||||
_lowPriorityFilterEnabled = UserPreference(key: .lowPriorityFilterEnabled, defaultValue: false, storage: store)
|
||||
_enableOnlySignedDeviceIsolationMode = UserPreference(key: .enableOnlySignedDeviceIsolationMode, defaultValue: false, storage: store)
|
||||
_knockingEnabled = UserPreference(key: .knockingEnabled, defaultValue: false, storage: store)
|
||||
_threadsEnabled = UserPreference(key: .threadsEnabled, defaultValue: false, storage: store)
|
||||
_roomThreadListEnabled = UserPreference(key: .roomThreadListEnabled, defaultValue: false, storage: store)
|
||||
_focusEventOnNotificationTap = UserPreference(key: .focusEventOnNotificationTap, defaultValue: false, storage: store)
|
||||
_linkPreviewsEnabled = UserPreference(key: .linkPreviewsEnabled, defaultValue: false, storage: store)
|
||||
_jumpToReadMarkerEnabled = UserPreference(key: .jumpToReadMarkerEnabled, defaultValue: false, storage: store)
|
||||
_linkNewDeviceEnabled = UserPreference(key: .linkNewDeviceEnabled, defaultValue: false, storage: store)
|
||||
_automaticBackPaginationEnabled = UserPreference(key: .automaticBackPaginationEnabled, defaultValue: false, storage: store)
|
||||
_clientPausingAndResumingEnabled = UserPreference(key: .clientPausingAndResumingEnabled, defaultValue: Self.appBuildType != .release, storage: VolatileUserDefaults())
|
||||
_developerOptionsEnabled = UserPreference(key: .developerOptionsEnabled, defaultValue: Self.appBuildType != .release, storage: store)
|
||||
}
|
||||
|
||||
static func volatile() -> AppSettings {
|
||||
AppSettings(store: VolatileUserDefaults())
|
||||
}
|
||||
}
|
||||
|
||||
extension AppSettings: CommonSettingsProtocol { }
|
||||
|
||||
private extension UserPreference {
|
||||
convenience init(key: AppSettings.UserDefaultsKeys, defaultValue: T, storage backingStorage: UserDefaultsProtocol, mode: Mode = .localOverRemote) {
|
||||
self.init(key: key as any PreferenceKeyable, defaultValue: defaultValue, storage: backingStorage, mode: mode)
|
||||
}
|
||||
|
||||
convenience init(key: AppSettings.UserDefaultsKeys, storage: UserDefaultsProtocol, mode: Mode = .localOverRemote) where T: ExpressibleByNilLiteral {
|
||||
self.init(key: key as any PreferenceKeyable, defaultValue: nil, storage: storage, mode: mode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user