Files
2025-11-19 14:19:17 +11:00

313 lines
13 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable
import Foundation
public extension UserDefaultsStorage {
static var standard: UserDefaultsConfig = Dependencies.create(identifier: "standard") { _ in
UserDefaults.standard
}
static var appGroup: UserDefaultsConfig = Dependencies.create(identifier: UserDefaults.applicationGroup) { _ in
UserDefaults(suiteName: UserDefaults.applicationGroup)!
}
}
// MARK: - UserDefaultsType
public protocol UserDefaultsType: AnyObject {
var allKeys: [String] { get }
func object(forKey defaultName: String) -> Any?
func string(forKey defaultName: String) -> String?
func array(forKey defaultName: String) -> [Any]?
func dictionary(forKey defaultName: String) -> [String : Any]?
func data(forKey defaultName: String) -> Data?
func stringArray(forKey defaultName: String) -> [String]?
func integer(forKey defaultName: String) -> Int
func float(forKey defaultName: String) -> Float
func double(forKey defaultName: String) -> Double
func bool(forKey defaultName: String) -> Bool
func url(forKey defaultName: String) -> URL?
func set(_ value: Any?, forKey defaultName: String)
func set(_ value: Int, forKey defaultName: String)
func set(_ value: Float, forKey defaultName: String)
func set(_ value: Double, forKey defaultName: String)
func set(_ value: Bool, forKey defaultName: String)
func set(_ url: URL?, forKey defaultName: String)
func removeObject(forKey defaultName: String)
func removeAll()
}
public extension UserDefaultsType {
func removeObject(forKey key: UserDefaults.BoolKey) { self.removeObject(forKey: key.rawValue) }
func removeObject(forKey key: UserDefaults.DateKey) { self.removeObject(forKey: key.rawValue) }
func removeObject(forKey key: UserDefaults.DoubleKey) { self.removeObject(forKey: key.rawValue) }
func removeObject(forKey key: UserDefaults.IntKey) { self.removeObject(forKey: key.rawValue) }
func removeObject(forKey key: UserDefaults.StringKey) { self.removeObject(forKey: key.rawValue) }
}
extension UserDefaults: UserDefaultsType {}
// MARK: - Convenience
public extension UserDefaults {
@ThreadSafeObject private static var cachedApplicationGroup: String = ""
// stringlint:ignore_contents
static let applicationGroup: String = {
guard !cachedApplicationGroup.isEmpty else {
let dynamicAppGroupsId: String = (Bundle.main.infoDictionary?["AppGroupsId"] as? String)
.defaulting(to: "group.com.loki-project.loki-messenger")
_cachedApplicationGroup.set(to: dynamicAppGroupsId)
return dynamicAppGroupsId
}
return cachedApplicationGroup
}()
var allKeys: [String] { Array(self.dictionaryRepresentation().keys) }
static func removeAll(using dependencies: Dependencies) {
UserDefaultsStorage.standard.createInstance(dependencies).removeAll()
UserDefaultsStorage.appGroup.createInstance(dependencies).removeAll()
}
func removeAll() {
let data: [String: Any] = self.dictionaryRepresentation()
data.forEach { key, _ in self.removeObject(forKey: key) }
self.synchronize() // Shouldn't be needed but better safe than sorry
}
}
// MARK: - UserDefaults Convenience
public extension Dependencies {
subscript(defaults defaults: UserDefaultsConfig, key key: UserDefaults.BoolKey) -> Bool {
get { return self[defaults: defaults].bool(forKey: key.rawValue) }
set {
self[defaults: defaults].set(newValue, forKey: key.rawValue)
self.notifyAsync(key: .userDefault(key), value: newValue)
}
}
subscript(defaults defaults: UserDefaultsConfig, key key: UserDefaults.DateKey) -> Date? {
get { return self[defaults: defaults].object(forKey: key.rawValue) as? Date }
set {
self[defaults: defaults].set(newValue, forKey: key.rawValue)
self.notifyAsync(key: .userDefault(key), value: newValue)
}
}
subscript(defaults defaults: UserDefaultsConfig, key key: UserDefaults.DoubleKey) -> Double {
get { return self[defaults: defaults].double(forKey: key.rawValue) }
set {
self[defaults: defaults].set(newValue, forKey: key.rawValue)
self.notifyAsync(key: .userDefault(key), value: newValue)
}
}
subscript(defaults defaults: UserDefaultsConfig, key key: UserDefaults.IntKey) -> Int {
get { return self[defaults: defaults].integer(forKey: key.rawValue) }
set {
self[defaults: defaults].set(newValue, forKey: key.rawValue)
self.notifyAsync(key: .userDefault(key), value: newValue)
}
}
subscript(defaults defaults: UserDefaultsConfig, key key: UserDefaults.StringKey) -> String? {
get { return self[defaults: defaults].string(forKey: key.rawValue) }
set {
self[defaults: defaults].set(newValue, forKey: key.rawValue)
self.notifyAsync(key: .userDefault(key), value: newValue)
}
}
}
// MARK: - UserDefault Values
public extension UserDefaults.BoolKey {
/// Indicates whether the user has seen the suggestion to enable link previews
static let hasSeenLinkPreviewSuggestion: UserDefaults.BoolKey = "hasSeenLinkPreviewSuggestion"
/// Indicates whether the user has seen the IP exposure warning when enabling calls
///
/// **Note:** This is currently not in use (it was decided that it's better to warn the user every time they enable calls instead
/// of just the first time)
static let hasSeenCallIPExposureWarning: UserDefaults.BoolKey = "hasSeenCallIPExposureWarning"
/// Indicates whether the user has seen the missed call tips modal
static let hasSeenCallMissedTips: UserDefaults.BoolKey = "hasSeenCallMissedTips"
/// Indicates whether the user is registered for APNS (ie. "Fast Mode" notifications)
static let isUsingFullAPNs: UserDefaults.BoolKey = "isUsingFullAPNs"
/// Indicates whether the device was unlinked from an account
///
/// **Note:** This doesn't seem to be properly used (we basically just maintain the existing value)
static let wasUnlinked: UserDefaults.BoolKey = "wasUnlinked"
/// Indicates whether the main app is active, this is set to `true` while the app is in the foreground and `false` when
/// the app is in the background
static let isMainAppActive: UserDefaults.BoolKey = "isMainAppActive"
/// Indicates whether there is an ongoing call
static let isCallOngoing: UserDefaults.BoolKey = "isCallOngoing"
/// Indicates whether we had the microphone permission the last time the app when to the background
static let lastSeenHasMicrophonePermission: UserDefaults.BoolKey = "lastSeenHasMicrophonePermission"
/// Indicates whether we had asked for the local network permission
static let hasRequestedLocalNetworkPermission: UserDefaults.BoolKey = "hasRequestedLocalNetworkPermission"
/// Indicates whether the local notification for token bonus is scheduled
static let isSessionNetworkPageNotificationScheduled: UserDefaults.BoolKey = "isSessionNetworkPageNotificationScheduled"
/// Indicates whether the user visited the Path screen
static let hasVisitedPathScreen: UserDefaults.BoolKey = "hasVisitedPathScreen"
/// Indicates whether the user changed the app theme
static let hasChangedTheme: UserDefaults.BoolKey = "hasChangedTheme"
/// Indicates whether the user pressed the donate button
static let hasPressedDonateButton: UserDefaults.BoolKey = "hasPressedDonateButton"
/// Indicates wheter app has already presented the user the app review prompt dialog
static let didShowAppReviewPrompt: UserDefaults.BoolKey = "didShowAppReviewPrompt"
/// Idicates whether app review prompt was ignored or no iteraction was done to dismiss it (closed app)
static let didActionAppReviewPrompt: UserDefaults.BoolKey = "didActionAppReviewPrompt"
/// Indicates wheter the user should be reminded to grant camera permission for calls
static let shouldRemindGrantingCameraPermissionForCalls: UserDefaults.BoolKey = "shouldRemindGrantingCameraPermissionForCalls"
/// Indicates wheter the app has shown the Pro expiring CTA
static let hasShownProExpiringCTA: UserDefaults.BoolKey = "hasShownProExpiringCTA"
/// Indicates wheter the app has shown the Pro expired CTA
static let hasShownProExpiredCTA: UserDefaults.BoolKey = "hasShownProExpiredCTA"
}
public extension UserDefaults.DateKey {
/// The date/time when we re-uploaded or extended the TTL of the users display picture (used for rate-limiting)
static let lastUserDisplayPictureRefresh: UserDefaults.DateKey = "lastProfilePictureUpload"
/// The date/time when any open group last had a successful poll (used as a fallback date/time if the open group hasn't been polled
/// this session)
static let lastOpen: UserDefaults.DateKey = "lastOpen"
/// The date/time when the last garbage collection was performed (used to rate-limit garbage collection)
static let lastGarbageCollection: UserDefaults.DateKey = "lastGarbageCollection"
/// The date/time when we received a call pre-offer (used to suppress call notifications which are too old)
static let lastCallPreOffer: UserDefaults.DateKey = "lastCallPreOffer"
/// The date/time when app review prompt will appear again
static let rateAppRetryDate: UserDefaults.DateKey = "rateAppRetryDate"
}
public extension UserDefaults.DoubleKey {
/// The timestamp when we last successfully uploaded the users push token (used to rate-limit calling our subscription endpoint)
static let lastDeviceTokenUpload: UserDefaults.DoubleKey = "lastDeviceTokenUploadTime"
}
public extension UserDefaults.IntKey {
/// The latest hardfork value returned when interacting with a service node
static let hardfork: UserDefaults.IntKey = "hardfork"
/// The latest softfork value returned when interacting with a service node
static let softfork: UserDefaults.IntKey = "softfork"
/// The id of the message that was just shared to
static let lastSharedMessageId: UserDefaults.IntKey = "lastSharedMessageId"
/// The number of attempts made to retry showing of app rating prompt
static let rateAppRetryAttemptCount: UserDefaults.IntKey = "rateAppRetryAttemptCount"
}
public extension UserDefaults.StringKey {
/// The most recently subscribed APNS token
static let deviceToken: UserDefaults.StringKey = "deviceToken"
/// The warning to show at the top of the app
static let topBannerWarningToShow: UserDefaults.StringKey = "topBannerWarningToShow"
/// The id of the thread that a message was just shared to
static let lastSharedThreadId: UserDefaults.StringKey = "lastSharedThreadId"
/// The app-icon name of the previously selected app icon disguise
static let lastSelectedAppIconDisguise: UserDefaults.StringKey = "lastSelectedAppIconDisguise"
}
// MARK: - Keys
public extension UserDefaults {
protocol Key: RawRepresentable, ExpressibleByStringLiteral, Hashable {
var rawValue: String { get }
init(_ rawValue: String)
}
struct BoolKey: Key {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
}
struct DateKey: Key {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
}
struct DoubleKey: Key {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
}
struct IntKey: Key {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
}
struct StringKey: Key {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
}
}
public extension UserDefaults.Key {
init?(rawValue: String) { self.init(rawValue) }
init(stringLiteral value: String) { self.init(value) }
init(unicodeScalarLiteral value: String) { self.init(value) }
init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
// MARK: - Observable Keys
public extension ObservableKey {
static func userDefault(_ key: UserDefaults.BoolKey) -> ObservableKey {
ObservableKey(key.rawValue, .userDefault)
}
static func userDefault(_ key: UserDefaults.DateKey) -> ObservableKey {
ObservableKey(key.rawValue, .userDefault)
}
static func userDefault(_ key: UserDefaults.DoubleKey) -> ObservableKey {
ObservableKey(key.rawValue, .userDefault)
}
static func userDefault(_ key: UserDefaults.IntKey) -> ObservableKey {
ObservableKey(key.rawValue, .userDefault)
}
static func userDefault(_ key: UserDefaults.StringKey) -> ObservableKey {
ObservableKey(key.rawValue, .userDefault)
}
}
public extension GenericObservableKey {
static let userDefault: GenericObservableKey = "userDefault"
}