Refactor and Scope user settings to pubkey
This commit is contained in:
@@ -9,6 +9,217 @@ import Foundation
|
||||
import Vault
|
||||
import UIKit
|
||||
|
||||
@propertyWrapper struct Setting<T: Equatable> {
|
||||
private let key: String
|
||||
private var value: T
|
||||
|
||||
init(key: String, default_value: T) {
|
||||
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
|
||||
if let loaded = UserDefaults.standard.object(forKey: self.key) as? T {
|
||||
self.value = loaded
|
||||
} else if let loaded = UserDefaults.standard.object(forKey: key) as? T {
|
||||
// try to load from deprecated non-pubkey-keyed setting
|
||||
self.value = loaded
|
||||
} else {
|
||||
self.value = default_value
|
||||
}
|
||||
}
|
||||
|
||||
var wrappedValue: T {
|
||||
get { return value }
|
||||
set {
|
||||
guard self.value != newValue else {
|
||||
return
|
||||
}
|
||||
self.value = newValue
|
||||
UserDefaults.standard.set(newValue, forKey: key)
|
||||
UserSettingsStore.shared!.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@propertyWrapper class StringSetting<T: StringCodable & Equatable> {
|
||||
private let key: String
|
||||
private var value: T
|
||||
|
||||
init(key: String, default_value: T) {
|
||||
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
|
||||
if let loaded = UserDefaults.standard.string(forKey: self.key), let val = T.init(from: loaded) {
|
||||
self.value = val
|
||||
} else if let loaded = UserDefaults.standard.string(forKey: key), let val = T.init(from: loaded) {
|
||||
// try to load from deprecated non-pubkey-keyed setting
|
||||
self.value = val
|
||||
} else {
|
||||
self.value = default_value
|
||||
}
|
||||
}
|
||||
|
||||
var wrappedValue: T {
|
||||
get { return value }
|
||||
set {
|
||||
guard self.value != newValue else {
|
||||
return
|
||||
}
|
||||
self.value = newValue
|
||||
UserDefaults.standard.set(newValue.to_string(), forKey: key)
|
||||
UserSettingsStore.shared!.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UserSettingsStore: ObservableObject {
|
||||
static var pubkey: String? = nil
|
||||
static var shared: UserSettingsStore? = nil
|
||||
|
||||
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
|
||||
var default_wallet: Wallet
|
||||
|
||||
@StringSetting(key: "default_media_uploader", default_value: .nostrBuild)
|
||||
var default_media_uploader: MediaUploader
|
||||
|
||||
@Setting(key: "show_wallet_selector", default_value: true)
|
||||
var show_wallet_selector: Bool
|
||||
|
||||
@Setting(key: "left_handed", default_value: false)
|
||||
var left_handed: Bool
|
||||
|
||||
@Setting(key: "always_show_images", default_value: false)
|
||||
var always_show_images: Bool
|
||||
|
||||
@Setting(key: "zap_vibration", default_value: true)
|
||||
var zap_vibration: Bool
|
||||
|
||||
@Setting(key: "zap_notification", default_value: true)
|
||||
var zap_notification: Bool
|
||||
|
||||
@Setting(key: "mention_notification", default_value: true)
|
||||
var mention_notification: Bool
|
||||
|
||||
@Setting(key: "repost_notification", default_value: true)
|
||||
var repost_notification: Bool
|
||||
|
||||
@Setting(key: "dm_notification", default_value: true)
|
||||
var dm_notification: Bool
|
||||
|
||||
@Setting(key: "like_notification", default_value: true)
|
||||
var like_notification: Bool
|
||||
|
||||
@Setting(key: "notification_only_from_following", default_value: false)
|
||||
var notification_only_from_following: Bool
|
||||
|
||||
@Setting(key: "translate_dms", default_value: false)
|
||||
var translate_dms: Bool
|
||||
|
||||
@Setting(key: "truncate_timeline_text", default_value: false)
|
||||
var truncate_timeline_text: Bool
|
||||
|
||||
@Setting(key: "truncate_mention_text", default_value: true)
|
||||
var truncate_mention_text: Bool
|
||||
|
||||
@Setting(key: "notification_indicators", default_value: NewEventsBits.all.rawValue)
|
||||
var notification_indicators: Int
|
||||
|
||||
@Setting(key: "auto_translate", default_value: true)
|
||||
var auto_translate: Bool
|
||||
|
||||
@Setting(key: "show_only_preferred_languages", default_value: false)
|
||||
var show_only_preferred_languages: Bool
|
||||
|
||||
@Setting(key: "onlyzaps_mode", default_value: false)
|
||||
var onlyzaps_mode: Bool
|
||||
|
||||
@Setting(key: "disable_animation", default_value: UIAccessibility.isReduceMotionEnabled)
|
||||
var disable_animation: Bool
|
||||
|
||||
@StringSetting(key: "translation_service", default_value: .none)
|
||||
var translation_service: TranslationService
|
||||
|
||||
@StringSetting(key: "deepl_plan", default_value: .free)
|
||||
var deepl_plan: DeepLPlan
|
||||
|
||||
var deepl_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if deepl_api_key == "" {
|
||||
try clearDeepLApiKey()
|
||||
} else {
|
||||
try saveDeepLApiKey(deepl_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Setting(key: "libretranslate_server", default_value: .vern)
|
||||
var libretranslate_server: LibreTranslateServer
|
||||
|
||||
@Setting(key: "libretranslate_url", default_value: "")
|
||||
var libretranslate_url: String
|
||||
|
||||
@Setting(key: "libretranslate_api_key", default_value: "")
|
||||
var libretranslate_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if libretranslate_api_key == "" {
|
||||
try clearLibreTranslateApiKey()
|
||||
} else {
|
||||
try saveLibreTranslateApiKey(libretranslate_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
do {
|
||||
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
} catch {
|
||||
deepl_api_key = ""
|
||||
}
|
||||
}
|
||||
|
||||
private func saveLibreTranslateApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearLibreTranslateApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func saveDeepLApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearDeepLApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
}
|
||||
|
||||
func can_translate(_ pubkey: String) -> Bool {
|
||||
switch translation_service {
|
||||
case .none:
|
||||
return false
|
||||
case .libretranslate:
|
||||
return URLComponents(string: libretranslate_url) != nil
|
||||
case .deepl:
|
||||
return deepl_api_key != ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DamusLibreTranslateKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "libretranslate_apikey"
|
||||
}
|
||||
|
||||
struct DamusDeepLKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "deepl_apikey"
|
||||
}
|
||||
|
||||
func should_show_wallet_selector(_ pubkey: String) -> Bool {
|
||||
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
|
||||
}
|
||||
@@ -69,12 +280,12 @@ private func get_translation_service(_ pubkey: String) -> TranslationService? {
|
||||
return TranslationService(rawValue: translation_service)
|
||||
}
|
||||
|
||||
private func get_deepl_plan(_ pubkey: String) -> DeepLPlan? {
|
||||
guard let server_name = UserDefaults.standard.string(forKey: "deepl_plan") else {
|
||||
return nil
|
||||
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
|
||||
if let url = server.model.url {
|
||||
return url
|
||||
}
|
||||
|
||||
return DeepLPlan(rawValue: server_name)
|
||||
|
||||
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
|
||||
}
|
||||
|
||||
private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? {
|
||||
@@ -84,302 +295,3 @@ private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer
|
||||
|
||||
return LibreTranslateServer(rawValue: server_name)
|
||||
}
|
||||
|
||||
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
|
||||
if let url = server.model.url {
|
||||
return url
|
||||
}
|
||||
|
||||
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
|
||||
}
|
||||
|
||||
class UserSettingsStore: ObservableObject {
|
||||
@Published var default_wallet: Wallet {
|
||||
didSet {
|
||||
UserDefaults.standard.set(default_wallet.rawValue, forKey: "default_wallet")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var default_media_uploader: MediaUploader {
|
||||
didSet {
|
||||
UserDefaults.standard.set(default_media_uploader.rawValue, forKey: "default_media_uploader")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var show_wallet_selector: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var left_handed: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(left_handed, forKey: "left_handed")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var always_show_images: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(always_show_images, forKey: "always_show_images")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var zap_vibration: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(zap_vibration, forKey: "zap_vibration")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var zap_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(zap_notification, forKey: "zap_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var mention_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(mention_notification, forKey: "mention_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var repost_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(repost_notification, forKey: "repost_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var dm_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(dm_notification, forKey: "dm_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var like_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(like_notification, forKey: "like_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var notification_only_from_following: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(notification_only_from_following, forKey: "notification_only_from_following")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var translate_dms: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(translate_dms, forKey: "translate_dms")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var truncate_timeline_text: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(truncate_timeline_text, forKey: "truncate_timeline_text")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var notification_indicators: Int {
|
||||
didSet {
|
||||
UserDefaults.standard.set(notification_indicators, forKey: "notification_indicators")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var truncate_mention_text: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var auto_translate: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(auto_translate, forKey: "auto_translate")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var show_only_preferred_languages: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var onlyzaps_mode: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(onlyzaps_mode, forKey: "onlyzaps_mode")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var translation_service: TranslationService {
|
||||
didSet {
|
||||
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var deepl_plan: DeepLPlan {
|
||||
didSet {
|
||||
UserDefaults.standard.set(deepl_plan.rawValue, forKey: "deepl_plan")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var deepl_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if deepl_api_key == "" {
|
||||
try clearDeepLApiKey()
|
||||
} else {
|
||||
try saveDeepLApiKey(deepl_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Published var libretranslate_server: LibreTranslateServer {
|
||||
didSet {
|
||||
if oldValue == libretranslate_server {
|
||||
return
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(libretranslate_server.rawValue, forKey: "libretranslate_server")
|
||||
|
||||
libretranslate_api_key = ""
|
||||
|
||||
if libretranslate_server == .custom {
|
||||
libretranslate_url = ""
|
||||
} else {
|
||||
libretranslate_url = libretranslate_server.model.url!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Published var libretranslate_url: String {
|
||||
didSet {
|
||||
UserDefaults.standard.set(libretranslate_url, forKey: "libretranslate_url")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var libretranslate_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if libretranslate_api_key == "" {
|
||||
try clearLibreTranslateApiKey()
|
||||
} else {
|
||||
try saveLibreTranslateApiKey(libretranslate_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Published var disable_animation: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(disable_animation, forKey: "disable_animation")
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
// TODO: pubkey-scoped settings
|
||||
let pubkey = ""
|
||||
self.default_wallet = get_default_wallet(pubkey)
|
||||
show_wallet_selector = should_show_wallet_selector(pubkey)
|
||||
always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false
|
||||
|
||||
default_media_uploader = get_media_uploader(pubkey)
|
||||
|
||||
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
||||
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
||||
zap_notification = UserDefaults.standard.object(forKey: "zap_notification") as? Bool ?? true
|
||||
mention_notification = UserDefaults.standard.object(forKey: "mention_notification") as? Bool ?? true
|
||||
repost_notification = UserDefaults.standard.object(forKey: "repost_notification") as? Bool ?? true
|
||||
like_notification = UserDefaults.standard.object(forKey: "like_notification") as? Bool ?? true
|
||||
dm_notification = UserDefaults.standard.object(forKey: "dm_notification") as? Bool ?? true
|
||||
notification_indicators = UserDefaults.standard.object(forKey: "notification_indicators") as? Int ?? NewEventsBits.all.rawValue
|
||||
notification_only_from_following = UserDefaults.standard.object(forKey: "notification_only_from_following") as? Bool ?? false
|
||||
translate_dms = UserDefaults.standard.object(forKey: "translate_dms") as? Bool ?? false
|
||||
truncate_timeline_text = UserDefaults.standard.object(forKey: "truncate_timeline_text") as? Bool ?? false
|
||||
truncate_mention_text = UserDefaults.standard.object(forKey: "truncate_mention_text") as? Bool ?? false
|
||||
disable_animation = should_disable_image_animation()
|
||||
auto_translate = UserDefaults.standard.object(forKey: "auto_translate") as? Bool ?? true
|
||||
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
|
||||
onlyzaps_mode = UserDefaults.standard.object(forKey: "onlyzaps_mode") as? Bool ?? false
|
||||
|
||||
// Note from @tyiu:
|
||||
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
||||
// Instead of throwing all Damus users onto feature immediately, allow for discovery of feature organically.
|
||||
// Also, we are connecting to servers listed as mirrors on the official LibreTranslate GitHub README that do not require API keys.
|
||||
// However, we have not asked them for permission to use, so we're trying to be good neighbors for now.
|
||||
// Opportunity: spin up dedicated trusted LibreTranslate server that requires an API key for any access (or higher rate limit access).
|
||||
if let translation_service = get_translation_service(pubkey) {
|
||||
self.translation_service = translation_service
|
||||
} else {
|
||||
self.translation_service = .none
|
||||
}
|
||||
|
||||
if let libretranslate_server = get_libretranslate_server(pubkey) {
|
||||
self.libretranslate_server = libretranslate_server
|
||||
self.libretranslate_url = get_libretranslate_url(pubkey, server: libretranslate_server) ?? ""
|
||||
} else {
|
||||
// Choose a random server to distribute load.
|
||||
libretranslate_server = .allCases.filter { $0 != .custom }.randomElement()!
|
||||
libretranslate_url = ""
|
||||
}
|
||||
|
||||
do {
|
||||
libretranslate_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
} catch {
|
||||
libretranslate_api_key = ""
|
||||
}
|
||||
|
||||
if let deepl_plan = get_deepl_plan(pubkey) {
|
||||
self.deepl_plan = deepl_plan
|
||||
} else {
|
||||
self.deepl_plan = .free
|
||||
}
|
||||
|
||||
do {
|
||||
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
} catch {
|
||||
deepl_api_key = ""
|
||||
}
|
||||
}
|
||||
|
||||
private func saveLibreTranslateApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearLibreTranslateApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func saveDeepLApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearDeepLApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
}
|
||||
|
||||
func can_translate(_ pubkey: String) -> Bool {
|
||||
switch translation_service {
|
||||
case .none:
|
||||
return false
|
||||
case .libretranslate:
|
||||
return URLComponents(string: libretranslate_url) != nil
|
||||
case .deepl:
|
||||
return deepl_api_key != ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DamusLibreTranslateKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "libretranslate_apikey"
|
||||
}
|
||||
|
||||
struct DamusDeepLKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "deepl_apikey"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user