KeySettingsView.swift (4736B)
1 // 2 // KeySettingsView.swift 3 // damus 4 // 5 // Created by William Casarin on 2023-04-05. 6 // 7 8 import SwiftUI 9 import LocalAuthentication 10 11 struct KeySettingsView: View { 12 let keypair: Keypair 13 14 @State var privkey: String 15 @State var privkey_copied: Bool = false 16 @State var pubkey_copied: Bool = false 17 @State var show_privkey: Bool = false 18 @State var has_authenticated_locally: Bool = false 19 20 @Environment(\.dismiss) var dismiss 21 22 init(keypair: Keypair) { 23 _privkey = State(initialValue: keypair.privkey?.nsec ?? "") 24 self.keypair = keypair 25 } 26 27 var ShowSecToggle: some View { 28 Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey) 29 .onChange(of: show_privkey) { newValue in 30 if newValue { 31 authenticate_locally(has_authenticated_locally) { success in 32 self.has_authenticated_locally = success 33 self.show_privkey = success 34 } 35 } 36 } 37 } 38 39 // TODO: (jb55) could be more general but not gonna worry about it atm 40 func CopyButton(is_pk: Bool) -> some View { 41 return Button(action: { 42 let copyKey = { 43 UIPasteboard.general.string = is_pk ? self.keypair.pubkey.npub : self.privkey 44 self.privkey_copied = !is_pk 45 self.pubkey_copied = is_pk 46 47 let generator = UIImpactFeedbackGenerator(style: .light) 48 generator.impactOccurred() 49 } 50 51 if is_pk { 52 copyKey() 53 return 54 } 55 56 if has_authenticated_locally { 57 copyKey() 58 return 59 } 60 61 authenticate_locally(has_authenticated_locally) { success in 62 self.has_authenticated_locally = success 63 if success { 64 copyKey() 65 } 66 } 67 }) { 68 let copied = is_pk ? self.pubkey_copied : self.privkey_copied 69 Image(copied ? "check-circle" : "copy2") 70 } 71 } 72 73 var body: some View { 74 Form { 75 Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) { 76 HStack { 77 Text(keypair.pubkey.npub) 78 79 CopyButton(is_pk: true) 80 } 81 .clipShape(RoundedRectangle(cornerRadius: 5)) 82 } 83 84 if let sec = keypair.privkey?.nsec { 85 Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) { 86 HStack { 87 if show_privkey == false || !has_authenticated_locally { 88 SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey) 89 .disabled(true) 90 } else { 91 Text(sec) 92 .clipShape(RoundedRectangle(cornerRadius: 5)) 93 } 94 95 CopyButton(is_pk: false) 96 } 97 98 ShowSecToggle 99 } 100 } 101 102 } 103 .navigationTitle(NSLocalizedString("Keys", comment: "Navigation title for managing keys.")) 104 .onReceive(handle_notify(.switched_timeline)) { _ in 105 dismiss() 106 } 107 } 108 } 109 110 struct KeySettingsView_Previews: PreviewProvider { 111 static var previews: some View { 112 let kp = generate_new_keypair() 113 KeySettingsView(keypair: kp.to_keypair()) 114 } 115 } 116 117 func authenticate_locally(_ has_authenticated_locally: Bool, completion: @escaping (Bool) -> Void) { 118 // Need to authenticate only once while ConfigView is presented 119 guard !has_authenticated_locally else { 120 completion(true) 121 return 122 } 123 let context = LAContext() 124 if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) { 125 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 126 DispatchQueue.main.async { 127 completion(success) 128 } 129 } 130 } else { 131 // If there's no authentication set up on the device, let the user copy the key without it 132 completion(true) 133 } 134 } 135