damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit aa559b2916e01c7721fce4ced2f4b6f41e3df3ba
parent 9bf8349db60a9f247aa02e29a1205c8bb7ec9716
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 21 Apr 2023 16:21:01 -0700

Refactor and Scope user settings to pubkey

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Mdamus/ContentView.swift | 9+++++++--
Mdamus/Models/DamusState.swift | 2++
Mdamus/Models/DeepLPlan.swift | 14+++++++++++++-
Mdamus/Models/TranslationService.swift | 14+++++++++++++-
Mdamus/Models/UserSettingsStore.swift | 468++++++++++++++++++++++++++++++++-----------------------------------------------
Mdamus/Models/Wallet.swift | 13++++++++++++-
Adamus/Util/StringCodable.swift | 13+++++++++++++
Mdamus/Views/AttachMediaUtility.swift | 14+++++++++++++-
Mdamus/Views/Profile/ProfileView.swift | 4++--
10 files changed, 269 insertions(+), 286 deletions(-)

diff --git 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 = "<group>"; }; 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = "<group>"; }; 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; }; + 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = "<group>"; }; 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; }; 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; }; 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; }; @@ -1008,6 +1010,7 @@ 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */, 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */, 4CDA128B29EB19C40006FA5A /* LocalNotification.swift */, + 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */, ); path = Util; sourceTree = "<group>"; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -9,218 +9,135 @@ import Foundation import Vault import UIKit -func should_show_wallet_selector(_ pubkey: String) -> Bool { - return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true -} - -func pk_setting_key(_ pubkey: String, key: String) -> String { - return "\(pubkey)_\(key)" -} - -func default_zap_setting_key(pubkey: String) -> String { - return pk_setting_key(pubkey, key: "default_zap_amount") -} - -func set_default_zap_amount(pubkey: String, amount: Int) { - let key = default_zap_setting_key(pubkey: pubkey) - UserDefaults.standard.setValue(amount, forKey: key) -} - -let fallback_zap_amount = 1000 - -func get_default_zap_amount(pubkey: String) -> Int { - let key = default_zap_setting_key(pubkey: pubkey) - let amt = UserDefaults.standard.integer(forKey: key) - if amt == 0 { - return fallback_zap_amount - } - return amt -} - -func should_disable_image_animation() -> Bool { - return (UserDefaults.standard.object(forKey: "disable_animation") as? Bool) - ?? UIAccessibility.isReduceMotionEnabled - } - -func get_default_wallet(_ pubkey: String) -> Wallet { - if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"), - let default_wallet = Wallet(rawValue: defaultWalletName) - { - return default_wallet - } else { - return .system_default_wallet - } -} - -func get_media_uploader(_ pubkey: String) -> MediaUploader { - if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"), - let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) { - return defaultMediaUploader - } else { - return .nostrBuild - } -} - -private func get_translation_service(_ pubkey: String) -> TranslationService? { - guard let translation_service = UserDefaults.standard.string(forKey: "translation_service") else { - return nil - } - - 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 - } - - return DeepLPlan(rawValue: server_name) -} - -private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? { - guard let server_name = UserDefaults.standard.string(forKey: "libretranslate_server") else { - return nil - } +@propertyWrapper struct Setting<T: Equatable> { + private let key: String + private var value: T - 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") + 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 } } - @Published var show_wallet_selector: Bool { - didSet { - UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector") + 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() } } +} - @Published var left_handed: Bool { - didSet { - UserDefaults.standard.set(left_handed, forKey: "left_handed") - } - } +@propertyWrapper class StringSetting<T: StringCodable & Equatable> { + private let key: String + private var value: T - @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") + 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 } } - @Published var translate_dms: Bool { - didSet { - UserDefaults.standard.set(translate_dms, forKey: "translate_dms") + 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() } } +} - @Published var truncate_timeline_text: Bool { - didSet { - UserDefaults.standard.set(truncate_timeline_text, forKey: "truncate_timeline_text") - } - } +class UserSettingsStore: ObservableObject { + static var pubkey: String? = nil + static var shared: UserSettingsStore? = nil - @Published var notification_indicators: Int { - didSet { - UserDefaults.standard.set(notification_indicators, forKey: "notification_indicators") - } - } + @StringSetting(key: "default_wallet", default_value: .system_default_wallet) + var default_wallet: Wallet - @Published var truncate_mention_text: Bool { - didSet { - UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text") - } - } + @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 - @Published var auto_translate: Bool { - didSet { - UserDefaults.standard.set(auto_translate, forKey: "auto_translate") - } - } + @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 - @Published var show_only_preferred_languages: Bool { - didSet { - UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages") - } - } + @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 - @Published var onlyzaps_mode: Bool { - didSet { - UserDefaults.standard.set(onlyzaps_mode, forKey: "onlyzaps_mode") - } - } + @Setting(key: "show_only_preferred_languages", default_value: false) + var show_only_preferred_languages: Bool - @Published var translation_service: TranslationService { - didSet { - UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service") - } - } + @Setting(key: "onlyzaps_mode", default_value: false) + var onlyzaps_mode: Bool + + @Setting(key: "disable_animation", default_value: UIAccessibility.isReduceMotionEnabled) + var disable_animation: Bool - @Published var deepl_plan: DeepLPlan { - didSet { - UserDefaults.standard.set(deepl_plan.rawValue, forKey: "deepl_plan") - } - } + @StringSetting(key: "translation_service", default_value: .none) + var translation_service: TranslationService - @Published var deepl_api_key: String { + @StringSetting(key: "deepl_plan", default_value: .free) + var deepl_plan: DeepLPlan + + var deepl_api_key: String { didSet { do { if deepl_api_key == "" { @@ -234,31 +151,14 @@ class UserSettingsStore: ObservableObject { } } - @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") - } - } + @Setting(key: "libretranslate_server", default_value: .vern) + var libretranslate_server: LibreTranslateServer + + @Setting(key: "libretranslate_url", default_value: "") + var libretranslate_url: String - @Published var libretranslate_api_key: String { + @Setting(key: "libretranslate_api_key", default_value: "") + var libretranslate_api_key: String { didSet { do { if libretranslate_api_key == "" { @@ -271,72 +171,8 @@ class UserSettingsStore: ObservableObject { } } } - - @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 { @@ -383,3 +219,79 @@ struct DamusDeepLKeychainConfiguration: KeychainConfiguration { 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 +} + +func pk_setting_key(_ pubkey: String, key: String) -> String { + return "\(pubkey)_\(key)" +} + +func default_zap_setting_key(pubkey: String) -> String { + return pk_setting_key(pubkey, key: "default_zap_amount") +} + +func set_default_zap_amount(pubkey: String, amount: Int) { + let key = default_zap_setting_key(pubkey: pubkey) + UserDefaults.standard.setValue(amount, forKey: key) +} + +let fallback_zap_amount = 1000 + +func get_default_zap_amount(pubkey: String) -> Int { + let key = default_zap_setting_key(pubkey: pubkey) + let amt = UserDefaults.standard.integer(forKey: key) + if amt == 0 { + return fallback_zap_amount + } + return amt +} + +func should_disable_image_animation() -> Bool { + return (UserDefaults.standard.object(forKey: "disable_animation") as? Bool) + ?? UIAccessibility.isReduceMotionEnabled + } + +func get_default_wallet(_ pubkey: String) -> Wallet { + if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"), + let default_wallet = Wallet(rawValue: defaultWalletName) + { + return default_wallet + } else { + return .system_default_wallet + } +} + +func get_media_uploader(_ pubkey: String) -> MediaUploader { + if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"), + let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) { + return defaultMediaUploader + } else { + return .nostrBuild + } +} + +private func get_translation_service(_ pubkey: String) -> TranslationService? { + guard let translation_service = UserDefaults.standard.string(forKey: "translation_service") else { + return nil + } + + return TranslationService(rawValue: translation_service) +} + +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 +} + +private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? { + guard let server_name = UserDefaults.standard.string(forKey: "libretranslate_server") else { + return nil + } + + return LibreTranslateServer(rawValue: server_name) +} diff --git 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 @@ -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 @@ -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 @@ -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") }