commit 9c8391b33b98ddfd87b3b289fb78a014518d9bb5
parent 89b2382ad765c4a2c522f08aba9b85c8536d75b9
Author: William Casarin <jb55@jb55.com>
Date: Wed, 5 Apr 2023 10:23:07 -0700
Refactor settings into subsections
Diffstat:
9 files changed, 516 insertions(+), 271 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -39,6 +39,11 @@
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
4C1A9A1A29DCA17E00516EAC /* ReplyCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */; };
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */; };
+ 4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */; };
+ 4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */; };
+ 4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */; };
+ 4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */; };
+ 4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */; };
4C216F32286E388800040376 /* DMChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F31286E388800040376 /* DMChatView.swift */; };
4C216F34286F5ACD00040376 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; };
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; };
@@ -403,6 +408,11 @@
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyCounter.swift; sourceTree = "<group>"; };
4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsView.swift; sourceTree = "<group>"; };
+ 4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceSettingsView.swift; sourceTree = "<group>"; };
+ 4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeySettingsView.swift; sourceTree = "<group>"; };
+ 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconLabel.swift; sourceTree = "<group>"; };
+ 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapSettingsView.swift; sourceTree = "<group>"; };
+ 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
4C216F31286E388800040376 /* DMChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMChatView.swift; sourceTree = "<group>"; };
4C216F33286F5ACD00040376 /* DMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMView.swift; sourceTree = "<group>"; };
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDismissKeyboard.swift; sourceTree = "<group>"; };
@@ -819,6 +829,10 @@
isa = PBXGroup;
children = (
4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */,
+ 4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */,
+ 4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */,
+ 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */,
+ 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
@@ -1087,6 +1101,7 @@
7CFF6316299FEFE5005D382A /* SelectableText.swift */,
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */,
4CE4F0F729DB7399005914DB /* ThiccDivider.swift */,
+ 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */,
);
path = Components;
sourceTree = "<group>";
@@ -1547,6 +1562,7 @@
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
+ 4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
@@ -1596,6 +1612,7 @@
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
+ 4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
@@ -1649,6 +1666,7 @@
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
4C06670B28FDE64700038D2A /* damus.c in Sources */,
+ 4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */,
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */,
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */,
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
@@ -1659,6 +1677,7 @@
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
+ 4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
4C75EFB528049D790006080F /* Relay.swift in Sources */,
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
@@ -1666,6 +1685,7 @@
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
7C0F392F29B57CAF0039859C /* Binding+.swift in Sources */,
+ 4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */,
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
diff --git a/damus/Components/IconLabel.swift b/damus/Components/IconLabel.swift
@@ -0,0 +1,44 @@
+//
+// IconLabel.swift
+// damus
+//
+// Created by William Casarin on 2023-04-05.
+//
+
+import SwiftUI
+import UIKit
+
+struct IconLabel: View {
+ let text: String
+ let img_name: String
+ let img_color: Color
+
+ init(_ text: String, img_name: String, color: Color) {
+ self.text = text
+ self.img_name = img_name
+ self.img_color = color
+ }
+
+ var body: some View {
+ HStack(spacing: 0) {
+ Image(systemName: img_name)
+ .foregroundColor(img_color)
+ .frame(width: 20)
+ .padding([.trailing], 20)
+ Text(text)
+ }
+ }}
+
+struct IconLabel_Previews: PreviewProvider {
+ static var previews: some View {
+ Form {
+ Section {
+ IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key.fill", color: .orange)
+
+ IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
+
+ IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "textformat", color: .red)
+ }
+ }
+ }
+}
diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift
@@ -169,6 +169,12 @@ class UserSettingsStore: ObservableObject {
UserDefaults.standard.set(truncate_timeline_text, forKey: "truncate_timeline_text")
}
}
+
+ @Published var truncate_mention_text: Bool {
+ didSet {
+ UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text")
+ }
+ }
@Published var auto_translate: Bool {
didSet {
@@ -270,6 +276,7 @@ class UserSettingsStore: ObservableObject {
dm_notification = UserDefaults.standard.object(forKey: "dm_notification") as? Bool ?? true
notification_only_from_following = UserDefaults.standard.object(forKey: "notification_only_from_following") 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 ?? true
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
diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift
@@ -17,26 +17,14 @@ struct ConfigView: View {
@State var confirm_logout: Bool = false
@State var delete_account_warning: Bool = false
@State var confirm_delete_account: Bool = false
- @State var show_privkey: Bool = false
- @State var has_authenticated_locally: Bool = false
- @State var show_api_key: Bool = false
- @State var privkey: String
- @State var privkey_copied: Bool = false
- @State var pubkey_copied: Bool = false
@State var delete_text: String = ""
- @State var default_zap_amount: String
@ObservedObject var settings: UserSettingsStore
-
- let generator = UIImpactFeedbackGenerator(style: .light)
private let DELETE_KEYWORD = "DELETE"
init(state: DamusState) {
self.state = state
- let zap_amt = get_default_zap_amount(pubkey: state.pubkey).map({ "\($0)" }) ?? "1000"
- _default_zap_amount = State(initialValue: zap_amt)
- _privkey = State(initialValue: self.state.keypair.privkey_bech32 ?? "")
_settings = ObservedObject(initialValue: state.settings)
}
@@ -44,202 +32,32 @@ struct ConfigView: View {
colorScheme == .light ? DamusColors.black : DamusColors.white
}
- func authenticateLocally(completion: @escaping (Bool) -> Void) {
- // Need to authenticate only once while ConfigView is presented
- guard !has_authenticated_locally else {
- completion(true)
- return
- }
- let context = LAContext()
- if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) {
- context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: NSLocalizedString("Local authentication to access private key", comment: "Face ID usage description shown when trying to access private key")) { success, error in
- DispatchQueue.main.async {
- has_authenticated_locally = success
- completion(success)
- }
- }
- } else {
- // If there's no authentication set up on the device, let the user copy the key without it
- has_authenticated_locally = true
- completion(true)
- }
- }
-
- // TODO: (jb55) could be more general but not gonna worry about it atm
- func CopyButton(is_pk: Bool) -> some View {
- return Button(action: {
- let copyKey = {
- UIPasteboard.general.string = is_pk ? self.state.keypair.pubkey_bech32 : self.privkey
- self.privkey_copied = !is_pk
- self.pubkey_copied = is_pk
- generator.impactOccurred()
- }
- if is_pk {
- // When trying to copy npub
- copyKey()
- } else {
- // When trying to copy nsec
- if has_authenticated_locally {
- copyKey()
- } else {
- authenticateLocally { success in
- if success {
- copyKey()
- }
- }
- }
- }
- }) {
- let copied = is_pk ? self.pubkey_copied : self.privkey_copied
- Image(systemName: copied ? "checkmark.circle" : "doc.on.doc")
- }
- }
-
var body: some View {
ZStack(alignment: .leading) {
Form {
- Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) {
- HStack {
- Text(state.keypair.pubkey_bech32)
-
- CopyButton(is_pk: true)
- }
- .clipShape(RoundedRectangle(cornerRadius: 5))
- }
-
- if let sec = state.keypair.privkey_bech32 {
- Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
- HStack {
- if show_privkey == false || !has_authenticated_locally {
- SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
- .disabled(true)
- } else {
- Text(sec)
- .clipShape(RoundedRectangle(cornerRadius: 5))
- }
-
- CopyButton(is_pk: false)
- }
-
- Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey)
- .onChange(of: show_privkey) { newValue in
- if newValue {
- authenticateLocally { success in
- show_privkey = success
- }
- }
- }
- }
- }
-
- Section(NSLocalizedString("Wallet and others", comment: "Section header for miscellaneous user configuration")) {
- Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)
- Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
- selection: $settings.default_wallet) {
- ForEach(Wallet.allCases, id: \.self) { wallet in
- Text(wallet.model.displayName)
- .tag(wallet.model.tag)
- }
- }
- Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $settings.left_handed)
- .toggleStyle(.switch)
- Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
- .toggleStyle(.switch)
- }
-
- NavigationLink(destination: NotificationSettingsView(settings: settings)) {
- Section(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration")) {
- }
- }
-
- Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
- TextField(String("1000"), text: $default_zap_amount)
- .keyboardType(.numberPad)
- .onReceive(Just(default_zap_amount)) { newValue in
- if let parsed = handle_string_amount(new_value: newValue) {
- self.default_zap_amount = String(parsed)
- set_default_zap_amount(pubkey: self.state.pubkey, amount: parsed)
- }
- }
- }
-
- Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
- Toggle(NSLocalizedString("Show only preferred languages on Universe feed", comment: "Toggle to show notes that are only in the device's preferred languages on the Universe feed and hide notes that are in other languages."), isOn: $settings.show_only_preferred_languages)
- .toggleStyle(.switch)
-
- Picker(NSLocalizedString("Service", comment: "Prompt selection of translation service provider."), selection: $settings.translation_service) {
- ForEach(TranslationService.allCases, id: \.self) { server in
- Text(server.model.displayName)
- .tag(server.model.tag)
- }
- }
-
- if settings.translation_service == .libretranslate {
- Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
- ForEach(LibreTranslateServer.allCases, id: \.self) { server in
- Text(server.model.displayName)
- .tag(server.model.tag)
- }
- }
-
- if settings.libretranslate_server == .custom {
- TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
- .disableAutocorrection(true)
- .autocapitalization(UITextAutocapitalizationType.none)
- }
-
- SecureField(NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server."), text: $settings.libretranslate_api_key)
- .disableAutocorrection(true)
- .disabled(settings.translation_service != .libretranslate)
- .autocapitalization(UITextAutocapitalizationType.none)
+ Section {
+ NavigationLink(destination: KeySettingsView(keypair: state.keypair)) {
+ IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key.fill", color: .purple)
}
-
- if settings.translation_service == .deepl {
- Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
- ForEach(DeepLPlan.allCases, id: \.self) { server in
- Text(server.model.displayName)
- .tag(server.model.tag)
- }
- }
-
- SecureField(NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server."), text: $settings.deepl_api_key)
- .disableAutocorrection(true)
- .disabled(settings.translation_service != .deepl)
- .autocapitalization(UITextAutocapitalizationType.none)
-
- if settings.deepl_api_key == "" {
- Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
- }
+
+ NavigationLink(destination: AppearanceSettingsView(settings: settings)) {
+ IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "textformat", color: .red)
}
-
- if settings.translation_service != .none {
- Toggle(NSLocalizedString("Automatically translate notes", comment: "Toggle to automatically translate notes."), isOn: $settings.auto_translate)
- .toggleStyle(.switch)
+
+ NavigationLink(destination: NotificationSettingsView(settings: settings)) {
+ IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
}
- }
-
- Section(NSLocalizedString("Images", comment: "Section title for images configuration.")) {
- Toggle(NSLocalizedString("Disable animations", comment: "Button to disable image animation"), isOn: $settings.disable_animation)
- .toggleStyle(.switch)
- .onChange(of: settings.disable_animation) { _ in
- clear_kingfisher_cache()
- }
- Toggle(NSLocalizedString("Always show images", comment: "Setting to always show and never blur images"), isOn: $settings.always_show_images)
- .toggleStyle(.switch)
-
- Button(NSLocalizedString("Clear Cache", comment: "Button to clear image cache.")) {
- clear_kingfisher_cache()
+
+ NavigationLink(destination: ZapSettingsView(pubkey: state.pubkey, settings: settings)) {
+ IconLabel(NSLocalizedString("Zaps", comment: "Section header for zap settings"), img_name: "bolt.fill", color: .orange)
}
- Picker(NSLocalizedString("Select image uploader", comment: "Prompt selection of user's image uploader"),
- selection: $settings.default_media_uploader) {
- ForEach(MediaUploader.allCases, id: \.self) { uploader in
- Text(uploader.model.displayName)
- .tag(uploader.model.tag)
- }
+ NavigationLink(destination: TranslationSettingsView(settings: settings)) {
+ IconLabel(NSLocalizedString("Translation", comment: "Section header for text and appearance settings"), img_name: "globe.americas.fill", color: .green)
}
}
+
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
Button(action: {
if state.keypair.privkey == nil {
@@ -314,80 +132,6 @@ struct ConfigView: View {
}
}
- var libretranslate_view: some View {
- VStack {
- Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
- ForEach(LibreTranslateServer.allCases, id: \.self) { server in
- Text(server.model.displayName)
- .tag(server.model.tag)
- }
- }
-
- TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
- .disableAutocorrection(true)
- .disabled(settings.libretranslate_server != .custom)
- .autocapitalization(UITextAutocapitalizationType.none)
- HStack {
- let libretranslate_api_key_placeholder = NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server.")
- if show_api_key {
- TextField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
- .disableAutocorrection(true)
- .autocapitalization(UITextAutocapitalizationType.none)
- if settings.libretranslate_api_key != "" {
- Button(NSLocalizedString("Hide API Key", comment: "Button to hide the LibreTranslate server API key.")) {
- show_api_key = false
- }
- }
- } else {
- SecureField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
- .disableAutocorrection(true)
- .autocapitalization(UITextAutocapitalizationType.none)
- if settings.libretranslate_api_key != "" {
- Button(NSLocalizedString("Show API Key", comment: "Button to show the LibreTranslate server API key.")) {
- show_api_key = true
- }
- }
- }
- }
- }
- }
-
- var deepl_view: some View {
- VStack {
- Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
- ForEach(DeepLPlan.allCases, id: \.self) { server in
- Text(server.model.displayName)
- .tag(server.model.tag)
- }
- }
-
- HStack {
- let deepl_api_key_placeholder = NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server.")
- if show_api_key {
- TextField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
- .disableAutocorrection(true)
- .autocapitalization(UITextAutocapitalizationType.none)
- if settings.deepl_api_key != "" {
- Button(NSLocalizedString("Hide API Key", comment: "Button to hide the DeepL translation API key.")) {
- show_api_key = false
- }
- }
- } else {
- SecureField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
- .disableAutocorrection(true)
- .autocapitalization(UITextAutocapitalizationType.none)
- if settings.deepl_api_key != "" {
- Button(NSLocalizedString("Show API Key", comment: "Button to show the DeepL translation API key.")) {
- show_api_key = true
- }
- }
- }
- if settings.deepl_api_key == "" {
- Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
- }
- }
- }
- }
}
struct ConfigView_Previews: PreviewProvider {
diff --git a/damus/Views/Settings/AppearanceSettingsView.swift b/damus/Views/Settings/AppearanceSettingsView.swift
@@ -0,0 +1,65 @@
+//
+// TextFormattingSettings.swift
+// damus
+//
+// Created by William Casarin on 2023-04-05.
+//
+
+import SwiftUI
+
+
+struct AppearanceSettingsView: View {
+ @ObservedObject var settings: UserSettingsStore
+ @Environment(\.dismiss) var dismiss
+
+ var body: some View {
+ Form {
+ Section(header: Text(NSLocalizedString("Text Truncation", comment: "Section header for damus text truncation user configuration"))) {
+ Toggle(NSLocalizedString("Truncate timeline text", comment: "Setting to truncate text in timeline"), isOn: $settings.truncate_timeline_text)
+ .toggleStyle(.switch)
+ Toggle(NSLocalizedString("Truncate notification mention text", comment: "Setting to truncate text in mention notifications"), isOn: $settings.truncate_mention_text)
+ .toggleStyle(.switch)
+ }
+
+ Section(header: Text(NSLocalizedString("Accessibility", comment: "Section header for accessibility settings"))) {
+ Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $settings.left_handed)
+ .toggleStyle(.switch)
+ }
+
+ Section(NSLocalizedString("Images", comment: "Section title for images configuration.")) {
+ Toggle(NSLocalizedString("Disable animations", comment: "Button to disable image animation"), isOn: $settings.disable_animation)
+ .toggleStyle(.switch)
+ .onChange(of: settings.disable_animation) { _ in
+ clear_kingfisher_cache()
+ }
+ Toggle(NSLocalizedString("Always show images", comment: "Setting to always show and never blur images"), isOn: $settings.always_show_images)
+ .toggleStyle(.switch)
+
+ Picker(NSLocalizedString("Select image uploader", comment: "Prompt selection of user's image uploader"),
+ selection: $settings.default_media_uploader) {
+ ForEach(MediaUploader.allCases, id: \.self) { uploader in
+ Text(uploader.model.displayName)
+ .tag(uploader.model.tag)
+ }
+ }
+
+ Button(NSLocalizedString("Clear Cache", comment: "Button to clear image cache.")) {
+ clear_kingfisher_cache()
+ }
+ }
+
+
+ }
+ .navigationTitle("Appearance")
+ .onReceive(handle_notify(.switched_timeline)) { _ in
+ dismiss()
+ }
+ }
+}
+
+
+struct TextFormattingSettings_Previews: PreviewProvider {
+ static var previews: some View {
+ AppearanceSettingsView(settings: UserSettingsStore())
+ }
+}
diff --git a/damus/Views/Settings/KeySettingsView.swift b/damus/Views/Settings/KeySettingsView.swift
@@ -0,0 +1,133 @@
+//
+// KeySettingsView.swift
+// damus
+//
+// Created by William Casarin on 2023-04-05.
+//
+
+import SwiftUI
+import LocalAuthentication
+
+struct KeySettingsView: View {
+ let keypair: Keypair
+
+ @State var privkey: String
+ @State var privkey_copied: Bool = false
+ @State var pubkey_copied: Bool = false
+ @State var show_privkey: Bool = false
+ @State var has_authenticated_locally: Bool = false
+
+ @Environment(\.dismiss) var dismiss
+
+ init(keypair: Keypair) {
+ _privkey = State(initialValue: keypair.privkey_bech32 ?? "")
+ self.keypair = keypair
+ }
+
+ var ShowSecToggle: some View {
+ Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey)
+ .onChange(of: show_privkey) { newValue in
+ if newValue {
+ authenticate_locally(has_authenticated_locally) { success in
+ show_privkey = success
+ }
+ }
+ }
+ }
+
+ // TODO: (jb55) could be more general but not gonna worry about it atm
+ func CopyButton(is_pk: Bool) -> some View {
+ return Button(action: {
+ let copyKey = {
+ UIPasteboard.general.string = is_pk ? self.keypair.pubkey_bech32 : self.privkey
+ self.privkey_copied = !is_pk
+ self.pubkey_copied = is_pk
+
+ let generator = UIImpactFeedbackGenerator(style: .light)
+ generator.impactOccurred()
+ }
+
+ if is_pk {
+ copyKey()
+ return
+ }
+
+ if has_authenticated_locally {
+ copyKey()
+ return
+ }
+
+ authenticate_locally(has_authenticated_locally) { success in
+ if success {
+ copyKey()
+ }
+ }
+ }) {
+ let copied = is_pk ? self.pubkey_copied : self.privkey_copied
+ Image(systemName: copied ? "checkmark.circle" : "doc.on.doc")
+ }
+ }
+
+ var body: some View {
+ Form {
+ Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) {
+ HStack {
+ Text(keypair.pubkey_bech32)
+
+ CopyButton(is_pk: true)
+ }
+ .clipShape(RoundedRectangle(cornerRadius: 5))
+ }
+
+ if let sec = keypair.privkey_bech32 {
+ Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
+ HStack {
+ if show_privkey == false || !has_authenticated_locally {
+ SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
+ .disabled(true)
+ } else {
+ Text(sec)
+ .clipShape(RoundedRectangle(cornerRadius: 5))
+ }
+
+ CopyButton(is_pk: false)
+ }
+
+ ShowSecToggle
+ }
+ }
+
+ }
+ .navigationTitle("Keys")
+ .onReceive(handle_notify(.switched_timeline)) { _ in
+ dismiss()
+ }
+ }
+}
+
+struct KeySettingsView_Previews: PreviewProvider {
+ static var previews: some View {
+ let kp = generate_new_keypair()
+ KeySettingsView(keypair: kp)
+ }
+}
+
+func authenticate_locally(_ has_authenticated_locally: Bool, completion: @escaping (Bool) -> Void) {
+ // Need to authenticate only once while ConfigView is presented
+ guard !has_authenticated_locally else {
+ completion(true)
+ return
+ }
+ let context = LAContext()
+ if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) {
+ context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: NSLocalizedString("Local authentication to access private key", comment: "Face ID usage description shown when trying to access private key")) { success, error in
+ DispatchQueue.main.async {
+ completion(success)
+ }
+ }
+ } else {
+ // If there's no authentication set up on the device, let the user copy the key without it
+ completion(true)
+ }
+}
+
diff --git a/damus/Views/Settings/NotificationSettingsView.swift b/damus/Views/Settings/NotificationSettingsView.swift
@@ -9,6 +9,8 @@ import SwiftUI
struct NotificationSettingsView: View {
@ObservedObject var settings: UserSettingsStore
+
+ @Environment(\.dismiss) var dismiss
var body: some View {
Form {
@@ -30,6 +32,10 @@ struct NotificationSettingsView: View {
.toggleStyle(.switch)
}
}
+ .navigationTitle("Notifications")
+ .onReceive(handle_notify(.switched_timeline)) { _ in
+ dismiss()
+ }
}
}
diff --git a/damus/Views/Settings/TranslationSettingsView.swift b/damus/Views/Settings/TranslationSettingsView.swift
@@ -0,0 +1,160 @@
+//
+// TranslationSettingsView.swift
+// damus
+//
+// Created by William Casarin on 2023-04-05.
+//
+
+import SwiftUI
+
+struct TranslationSettingsView: View {
+ @ObservedObject var settings: UserSettingsStore
+
+ @State var show_api_key: Bool = false
+
+ @Environment(\.dismiss) var dismiss
+
+ var body: some View {
+ Form {
+ Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
+ Toggle(NSLocalizedString("Show only preferred languages on Universe feed", comment: "Toggle to show notes that are only in the device's preferred languages on the Universe feed and hide notes that are in other languages."), isOn: $settings.show_only_preferred_languages)
+ .toggleStyle(.switch)
+
+ Picker(NSLocalizedString("Service", comment: "Prompt selection of translation service provider."), selection: $settings.translation_service) {
+ ForEach(TranslationService.allCases, id: \.self) { server in
+ Text(server.model.displayName)
+ .tag(server.model.tag)
+ }
+ }
+
+ if settings.translation_service == .libretranslate {
+ Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
+ ForEach(LibreTranslateServer.allCases, id: \.self) { server in
+ Text(server.model.displayName)
+ .tag(server.model.tag)
+ }
+ }
+
+ if settings.libretranslate_server == .custom {
+ TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
+ .disableAutocorrection(true)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ }
+
+ SecureField(NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server."), text: $settings.libretranslate_api_key)
+ .disableAutocorrection(true)
+ .disabled(settings.translation_service != .libretranslate)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ }
+
+ if settings.translation_service == .deepl {
+ Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
+ ForEach(DeepLPlan.allCases, id: \.self) { server in
+ Text(server.model.displayName)
+ .tag(server.model.tag)
+ }
+ }
+
+ SecureField(NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server."), text: $settings.deepl_api_key)
+ .disableAutocorrection(true)
+ .disabled(settings.translation_service != .deepl)
+ .autocapitalization(UITextAutocapitalizationType.none)
+
+ if settings.deepl_api_key == "" {
+ Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
+ }
+ }
+
+ if settings.translation_service != .none {
+ Toggle(NSLocalizedString("Automatically translate notes", comment: "Toggle to automatically translate notes."), isOn: $settings.auto_translate)
+ .toggleStyle(.switch)
+ }
+ }
+ }
+ .navigationTitle("Translation")
+ .onReceive(handle_notify(.switched_timeline)) { _ in
+ dismiss()
+ }
+ }
+
+ var libretranslate_view: some View {
+ VStack {
+ Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
+ ForEach(LibreTranslateServer.allCases, id: \.self) { server in
+ Text(server.model.displayName)
+ .tag(server.model.tag)
+ }
+ }
+
+ TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
+ .disableAutocorrection(true)
+ .disabled(settings.libretranslate_server != .custom)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ HStack {
+ let libretranslate_api_key_placeholder = NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server.")
+ if show_api_key {
+ TextField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
+ .disableAutocorrection(true)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ if settings.libretranslate_api_key != "" {
+ Button(NSLocalizedString("Hide API Key", comment: "Button to hide the LibreTranslate server API key.")) {
+ show_api_key = false
+ }
+ }
+ } else {
+ SecureField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
+ .disableAutocorrection(true)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ if settings.libretranslate_api_key != "" {
+ Button(NSLocalizedString("Show API Key", comment: "Button to show the LibreTranslate server API key.")) {
+ show_api_key = true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ var deepl_view: some View {
+ VStack {
+ Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
+ ForEach(DeepLPlan.allCases, id: \.self) { server in
+ Text(server.model.displayName)
+ .tag(server.model.tag)
+ }
+ }
+
+ HStack {
+ let deepl_api_key_placeholder = NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server.")
+ if show_api_key {
+ TextField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
+ .disableAutocorrection(true)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ if settings.deepl_api_key != "" {
+ Button(NSLocalizedString("Hide API Key", comment: "Button to hide the DeepL translation API key.")) {
+ show_api_key = false
+ }
+ }
+ } else {
+ SecureField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
+ .disableAutocorrection(true)
+ .autocapitalization(UITextAutocapitalizationType.none)
+ if settings.deepl_api_key != "" {
+ Button(NSLocalizedString("Show API Key", comment: "Button to show the DeepL translation API key.")) {
+ show_api_key = true
+ }
+ }
+ }
+ if settings.deepl_api_key == "" {
+ Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
+ }
+ }
+ }
+ }
+}
+
+struct TranslationSettingsView_Previews: PreviewProvider {
+ static var previews: some View {
+ TranslationSettingsView(settings: UserSettingsStore())
+ }
+}
diff --git a/damus/Views/Settings/ZapSettingsView.swift b/damus/Views/Settings/ZapSettingsView.swift
@@ -0,0 +1,66 @@
+//
+// WalletSettingsView.swift
+// damus
+//
+// Created by William Casarin on 2023-04-05.
+//
+
+import SwiftUI
+import Combine
+
+struct ZapSettingsView: View {
+ let pubkey: String
+ @ObservedObject var settings: UserSettingsStore
+
+ @State var default_zap_amount: String
+ @Environment(\.dismiss) var dismiss
+
+ init(pubkey: String, settings: UserSettingsStore) {
+ self.pubkey = pubkey
+ let zap_amt = get_default_zap_amount(pubkey: pubkey).map({ "\($0)" }) ?? "1000"
+ _default_zap_amount = State(initialValue: zap_amt)
+ self._settings = ObservedObject(initialValue: settings)
+ }
+
+ var body: some View {
+ Form {
+ Section("Wallet") {
+
+ Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)
+ Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
+ selection: $settings.default_wallet) {
+ ForEach(Wallet.allCases, id: \.self) { wallet in
+ Text(wallet.model.displayName)
+ .tag(wallet.model.tag)
+ }
+ }
+ }
+
+ Section("Zaps") {
+ Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
+ .toggleStyle(.switch)
+ }
+
+ Section("Default Zap Amount in sats") {
+ TextField(String("1000"), text: $default_zap_amount)
+ .keyboardType(.numberPad)
+ .onReceive(Just(default_zap_amount)) { newValue in
+ if let parsed = handle_string_amount(new_value: newValue) {
+ self.default_zap_amount = String(parsed)
+ set_default_zap_amount(pubkey: self.pubkey, amount: parsed)
+ }
+ }
+ }
+ }
+ .navigationTitle("Zaps")
+ .onReceive(handle_notify(.switched_timeline)) { _ in
+ dismiss()
+ }
+ }
+}
+
+struct WalletSettingsView_Previews: PreviewProvider {
+ static var previews: some View {
+ ZapSettingsView(pubkey: "pubkey", settings: UserSettingsStore())
+ }
+}