// // 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?.nsec ?? "") 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 self.has_authenticated_locally = success self.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.npub : 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 self.has_authenticated_locally = success if success { copyKey() } } }) { let copied = is_pk ? self.pubkey_copied : self.privkey_copied Image(copied ? "check-circle" : "copy2") } } var body: some View { Form { Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) { HStack { Text(keypair.pubkey.npub) CopyButton(is_pk: true) } .clipShape(RoundedRectangle(cornerRadius: 5)) } if let sec = keypair.privkey?.nsec { 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) .privacySensitive() .clipShape(RoundedRectangle(cornerRadius: 5)) } CopyButton(is_pk: false) } ShowSecToggle } } } .navigationTitle(NSLocalizedString("Keys", comment: "Navigation title for managing 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.to_keypair()) } } 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) } }