Refactor and Scope user settings to pubkey
This commit is contained in:
@@ -155,6 +155,7 @@
|
|||||||
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
|
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
|
||||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.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 */; };
|
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
||||||
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1008,6 +1010,7 @@
|
|||||||
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
|
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
|
||||||
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
|
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
|
||||||
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */,
|
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */,
|
||||||
|
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1524,6 +1527,7 @@
|
|||||||
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
|
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
|
||||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
||||||
|
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
||||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
||||||
|
|||||||
@@ -679,6 +679,11 @@ struct ContentView: View {
|
|||||||
|
|
||||||
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
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,
|
self.damus_state = DamusState(pool: pool,
|
||||||
keypair: keypair,
|
keypair: keypair,
|
||||||
likes: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
@@ -690,7 +695,7 @@ struct ContentView: View {
|
|||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
zaps: Zaps(our_pubkey: pubkey),
|
zaps: Zaps(our_pubkey: pubkey),
|
||||||
lnurls: LNUrls(),
|
lnurls: LNUrls(),
|
||||||
settings: UserSettingsStore(),
|
settings: settings,
|
||||||
relay_filters: relay_filters,
|
relay_filters: relay_filters,
|
||||||
relay_metadata: metadatas,
|
relay_metadata: metadatas,
|
||||||
drafts: Drafts(),
|
drafts: Drafts(),
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ struct DamusState {
|
|||||||
keypair.privkey != nil
|
keypair.privkey != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var settings_pubkey: String? = nil
|
||||||
|
|
||||||
static var empty: DamusState {
|
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))) }
|
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))) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,19 @@
|
|||||||
|
|
||||||
import Foundation
|
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 }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
|
|||||||
@@ -7,7 +7,19 @@
|
|||||||
|
|
||||||
import Foundation
|
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 }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
|
|||||||
@@ -9,6 +9,217 @@ import Foundation
|
|||||||
import Vault
|
import Vault
|
||||||
import UIKit
|
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 {
|
func should_show_wallet_selector(_ pubkey: String) -> Bool {
|
||||||
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
|
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)
|
return TranslationService(rawValue: translation_service)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func get_deepl_plan(_ pubkey: String) -> DeepLPlan? {
|
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
|
||||||
guard let server_name = UserDefaults.standard.string(forKey: "deepl_plan") else {
|
if let url = server.model.url {
|
||||||
return nil
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
return DeepLPlan(rawValue: server_name)
|
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? {
|
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)
|
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"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum Wallet: String, CaseIterable, Identifiable {
|
enum Wallet: String, CaseIterable, Identifiable, StringCodable {
|
||||||
var id: String { self.rawValue }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
@@ -20,6 +20,17 @@ enum Wallet: String, CaseIterable, Identifiable {
|
|||||||
var image: String
|
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
|
// New url prefixes needed to be added to LSApplicationQueriesSchemes
|
||||||
case system_default_wallet
|
case system_default_wallet
|
||||||
case strike
|
case strike
|
||||||
|
|||||||
13
damus/Util/StringCodable.swift
Normal file
13
damus/Util/StringCodable.swift
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -89,11 +89,23 @@ extension NSMutableData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaUploader: String, CaseIterable, Identifiable {
|
enum MediaUploader: String, CaseIterable, Identifiable, StringCodable {
|
||||||
var id: String { self.rawValue }
|
var id: String { self.rawValue }
|
||||||
case nostrBuild
|
case nostrBuild
|
||||||
case nostrImg
|
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 {
|
var nameParam: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .nostrBuild:
|
case .nostrBuild:
|
||||||
|
|||||||
@@ -266,9 +266,9 @@ struct ProfileView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(addr, systemImage: "doc.on.doc")
|
Label(addr, systemImage: "doc.on.doc")
|
||||||
}
|
}
|
||||||
} else if let lnurl = profile.lud06 {
|
} else if let lnurl = profile.lnurl {
|
||||||
Button {
|
Button {
|
||||||
UIPasteboard.general.string = profile.lnurl ?? ""
|
UIPasteboard.general.string = lnurl
|
||||||
} label: {
|
} label: {
|
||||||
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
|
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user