QRSheet.swift (5172B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import CodeScanner 10 import SymLog 11 import AVFoundation 12 13 struct QRSheet: View { 14 private let symLog = SymLogV(0) 15 let stack: CallStack 16 let selectedBalance: Balance? 17 @Binding var scannedSomething: Bool 18 19 @EnvironmentObject private var model: WalletModel 20 @AppStorage("minimalistic") var minimalistic: Bool = false 21 22 @State private var scannedCode: String? 23 @State private var isTorchOn: Bool = false 24 25 var body: some View { 26 #if PRINT_CHANGES 27 let _ = Self._printChanges() 28 let _ = symLog.vlog(scannedCode) // just to get the # to compare it with .onAppear & onDisappear 29 #endif 30 Group { 31 let errorAnnouncement = String(localized: "Error while scanning QR code", comment: "a11y") 32 if scannedCode != nil { 33 if let scannedURL = scannedCode!.toURL { 34 let scheme = scannedURL.scheme 35 if scheme?.lowercased() == "taler" { 36 URLSheet(stack.push(), 37 selectedBalance: selectedBalance, 38 sheetURL: scannedURL) 39 } else { 40 // TODO: error logging? 41 ErrorView(stack.push(), 42 title: String(localized: "Scanned QR is no talerURI"), 43 message: scannedURL.absoluteString, 44 copyable: true) { 45 scannedSomething = false 46 dismissTop(stack.push()) 47 } 48 } 49 } else { 50 ErrorView(stack.push(), 51 title: String(localized: "Scanned QR is no URL"), 52 message: scannedCode, 53 copyable: true 54 ) { 55 scannedSomething = false 56 dismissTop(stack.push()) 57 } 58 } 59 } else { 60 let codeScannerView = CodeScannerView(codeTypes: [AVMetadataObject.ObjectType.qr], 61 showViewfinder: true, 62 isTorchOn: isTorchOn 63 ) { response in 64 let closingAnnouncement: String 65 switch response { 66 case .success(let result): 67 symLog.log("Found code: \(result.string)") 68 scannedSomething = true 69 scannedCode = result.string 70 closingAnnouncement = String(localized: "QR code recognized", comment: "a11y") 71 case .failure(let error): 72 // TODO: errorAlert 73 scannedSomething = false 74 model.setError(error) 75 closingAnnouncement = errorAnnouncement 76 } 77 announce(closingAnnouncement) 78 } 79 80 if minimalistic { 81 codeScannerView 82 .onTapGesture { isTorchOn.toggle() } 83 } else if #available(iOS 16.4, *) { 84 codeScannerView 85 .toolbar { 86 ToolbarItem(placement: .topBarLeading) { 87 Button { 88 dismissTop(stack.push()) 89 } label: { 90 Image(systemName: XMARK) 91 } 92 .accessibilityLabel(Text("Dismiss QR scanner", comment: "a11y")) 93 // .accessibilityHidden(true) // VoiceOver has its own "Dismiss Popup" button 94 } 95 ToolbarItem(placement: .topBarTrailing) { 96 let a11yValue = isTorchOn ? String(localized: "on", comment: "a11y") 97 : String(localized: "off", comment: "a11y") 98 Button { 99 isTorchOn.toggle() 100 } label: { 101 Image(systemName: isTorchOn ? LIGHT_ON : LIGHT_OFF) 102 } 103 .accessibilityLabel(Text("Torch for QR code scanning", comment: "a11y")) 104 .accessibilityValue(Text(a11yValue)) 105 } 106 } 107 .navigationBarTitleDisplayMode(.inline) 108 .toolbarBackground(.gray) 109 .toolbarBackground(.visible) 110 // .toolbarBackground(.hidden) 111 } else { 112 codeScannerView 113 .onTapGesture { isTorchOn.toggle() } 114 } 115 } 116 } 117 } 118 }