Refactor settings into subsections
This commit is contained in:
@@ -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 */,
|
||||
|
||||
44
damus/Components/IconLabel.swift
Normal file
44
damus/Components/IconLabel.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
Section {
|
||||
NavigationLink(destination: KeySettingsView(keypair: state.keypair)) {
|
||||
IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key.fill", color: .purple)
|
||||
}
|
||||
|
||||
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: AppearanceSettingsView(settings: settings)) {
|
||||
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "textformat", color: .red)
|
||||
}
|
||||
|
||||
NavigationLink(destination: NotificationSettingsView(settings: settings)) {
|
||||
IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
|
||||
}
|
||||
|
||||
NavigationLink(destination: ZapSettingsView(pubkey: state.pubkey, settings: settings)) {
|
||||
IconLabel(NSLocalizedString("Zaps", comment: "Section header for zap settings"), img_name: "bolt.fill", color: .orange)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
65
damus/Views/Settings/AppearanceSettingsView.swift
Normal file
65
damus/Views/Settings/AppearanceSettingsView.swift
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
133
damus/Views/Settings/KeySettingsView.swift
Normal file
133
damus/Views/Settings/KeySettingsView.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
160
damus/Views/Settings/TranslationSettingsView.swift
Normal file
160
damus/Views/Settings/TranslationSettingsView.swift
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
66
damus/Views/Settings/ZapSettingsView.swift
Normal file
66
damus/Views/Settings/ZapSettingsView.swift
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user