From aa559b2916e01c7721fce4ced2f4b6f41e3df3ba Mon Sep 17 00:00:00 2001 From: William Casarin Date: Fri, 21 Apr 2023 16:21:01 -0700 Subject: [PATCH] Refactor and Scope user settings to pubkey --- damus.xcodeproj/project.pbxproj | 4 + damus/ContentView.swift | 9 +- damus/Models/DamusState.swift | 2 + damus/Models/DeepLPlan.swift | 14 +- damus/Models/TranslationService.swift | 14 +- damus/Models/UserSettingsStore.swift | 520 +++++++++++--------------- damus/Models/Wallet.swift | 13 +- damus/Util/StringCodable.swift | 13 + damus/Views/AttachMediaUtility.swift | 14 +- damus/Views/Profile/ProfileView.swift | 4 +- 10 files changed, 295 insertions(+), 312 deletions(-) create mode 100644 damus/Util/StringCodable.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 9479e0d2..945c5f42 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; }; 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; }; 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; }; + 4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */; }; 4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; }; 4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; }; 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; @@ -563,6 +564,7 @@ 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = ""; }; 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = ""; }; 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = ""; }; + 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = ""; }; 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = ""; }; 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = ""; }; 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = ""; }; @@ -1008,6 +1010,7 @@ 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */, 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */, 4CDA128B29EB19C40006FA5A /* LocalNotification.swift */, + 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */, ); path = Util; sourceTree = ""; @@ -1524,6 +1527,7 @@ 4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */, 4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */, 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */, + 4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */, 4C75EFB92804A2740006080F /* EventView.swift in Sources */, 4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */, 3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 58433355..1866c254 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -678,7 +678,12 @@ struct ContentView: View { } pool.register_handler(sub_id: sub_id, handler: home.handle_event) - + + // dumb stuff needed for property wrappers + UserSettingsStore.pubkey = pubkey + let settings = UserSettingsStore() + UserSettingsStore.shared = settings + self.damus_state = DamusState(pool: pool, keypair: keypair, likes: EventCounter(our_pubkey: pubkey), @@ -690,7 +695,7 @@ struct ContentView: View { previews: PreviewCache(), zaps: Zaps(our_pubkey: pubkey), lnurls: LNUrls(), - settings: UserSettingsStore(), + settings: settings, relay_filters: relay_filters, relay_metadata: metadatas, drafts: Drafts(), diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift index bef157fe..c62d5053 100644 --- a/damus/Models/DamusState.swift +++ b/damus/Models/DamusState.swift @@ -39,6 +39,8 @@ struct DamusState { keypair.privkey != nil } + static var settings_pubkey: String? = nil + static var empty: DamusState { return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil))) } } diff --git a/damus/Models/DeepLPlan.swift b/damus/Models/DeepLPlan.swift index c5c90d5a..e3d68949 100644 --- a/damus/Models/DeepLPlan.swift +++ b/damus/Models/DeepLPlan.swift @@ -7,7 +7,19 @@ import Foundation -enum DeepLPlan: String, CaseIterable, Identifiable { +enum DeepLPlan: String, CaseIterable, Identifiable, StringCodable { + init?(from string: String) { + guard let dl = DeepLPlan(rawValue: string) else { + return nil + } + + self = dl + } + + func to_string() -> String { + return self.rawValue + } + var id: String { self.rawValue } struct Model: Identifiable, Hashable { diff --git a/damus/Models/TranslationService.swift b/damus/Models/TranslationService.swift index 0561c13c..6539a6c3 100644 --- a/damus/Models/TranslationService.swift +++ b/damus/Models/TranslationService.swift @@ -7,7 +7,19 @@ import Foundation -enum TranslationService: String, CaseIterable, Identifiable { +enum TranslationService: String, CaseIterable, Identifiable, StringCodable { + init?(from string: String) { + guard let ts = TranslationService(rawValue: string) else { + return nil + } + + self = ts + } + + func to_string() -> String { + return self.rawValue + } + var id: String { self.rawValue } struct Model: Identifiable, Hashable { diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift index 77336773..bc9beb6e 100644 --- a/damus/Models/UserSettingsStore.swift +++ b/damus/Models/UserSettingsStore.swift @@ -9,6 +9,217 @@ import Foundation import Vault import UIKit +@propertyWrapper struct Setting { + 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 { + 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" -} diff --git a/damus/Models/Wallet.swift b/damus/Models/Wallet.swift index d436135d..30682dfc 100644 --- a/damus/Models/Wallet.swift +++ b/damus/Models/Wallet.swift @@ -7,7 +7,7 @@ import Foundation -enum Wallet: String, CaseIterable, Identifiable { +enum Wallet: String, CaseIterable, Identifiable, StringCodable { var id: String { self.rawValue } struct Model: Identifiable, Hashable { @@ -20,6 +20,17 @@ enum Wallet: String, CaseIterable, Identifiable { var image: String } + func to_string() -> String { + return rawValue + } + + init?(from string: String) { + guard let w = Wallet(rawValue: string) else { + return nil + } + self = w + } + // New url prefixes needed to be added to LSApplicationQueriesSchemes case system_default_wallet case strike diff --git a/damus/Util/StringCodable.swift b/damus/Util/StringCodable.swift new file mode 100644 index 00000000..6ae2efd0 --- /dev/null +++ b/damus/Util/StringCodable.swift @@ -0,0 +1,13 @@ +// +// StringCodable.swift +// damus +// +// Created by William Casarin on 2023-04-21. +// + +import Foundation + +protocol StringCodable { + init?(from string: String) + func to_string() -> String +} diff --git a/damus/Views/AttachMediaUtility.swift b/damus/Views/AttachMediaUtility.swift index 589056f6..35fa28a2 100644 --- a/damus/Views/AttachMediaUtility.swift +++ b/damus/Views/AttachMediaUtility.swift @@ -89,10 +89,22 @@ extension NSMutableData { } } -enum MediaUploader: String, CaseIterable, Identifiable { +enum MediaUploader: String, CaseIterable, Identifiable, StringCodable { var id: String { self.rawValue } case nostrBuild case nostrImg + + init?(from string: String) { + guard let mu = MediaUploader(rawValue: string) else { + return nil + } + + self = mu + } + + func to_string() -> String { + return rawValue + } var nameParam: String { switch self { diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift index 7cf97c4b..276c13d1 100644 --- a/damus/Views/Profile/ProfileView.swift +++ b/damus/Views/Profile/ProfileView.swift @@ -266,9 +266,9 @@ struct ProfileView: View { } label: { Label(addr, systemImage: "doc.on.doc") } - } else if let lnurl = profile.lud06 { + } else if let lnurl = profile.lnurl { Button { - UIPasteboard.general.string = profile.lnurl ?? "" + UIPasteboard.general.string = lnurl } label: { Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc") }