taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

QRSheet.swift (5908B)


      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 urlToOpen: URL? = nil
     24     @State private var isTorchOn: Bool = false
     25 
     26     func codeToURL(_ code: String) -> URL? {
     27         if let scannedURL = URL(string: code) {
     28             return scannedURL
     29         }
     30         if let encodedScan = code.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
     31             if let encodedURL = URL(string: encodedScan) {
     32                 return encodedURL
     33             }
     34         }
     35         return nil
     36     }
     37 
     38     var body: some View {
     39 #if PRINT_CHANGES
     40         let _ = Self._printChanges()
     41         let _ = symLog.vlog(scannedCode)       // just to get the # to compare it with .onAppear & onDisappear
     42 #endif
     43         Group {
     44             let errorAnnouncement = String(localized: "Error while scanning QR code", comment: "a11y")
     45             if scannedCode != nil {
     46                 // we need to evaluate the scanned code again, because urlToOpen will be set to nil
     47                 if let scannedURL = codeToURL(scannedCode!) {
     48                     let scheme = scannedURL.scheme
     49                     if scheme?.lowercased() == "taler" {
     50                         URLSheet(stack: stack.push(),
     51                        selectedBalance: selectedBalance,
     52                              urlToOpen: $urlToOpen)         // !!! will set @Binding to nil
     53                     } else {
     54 //                        let _ = print(scannedURL)       // TODO: error logging
     55                         ErrorView(stack.push(),
     56                                   title: String(localized: "Scanned QR is no talerURI"),
     57                                 message: scannedURL.absoluteString,
     58                                copyable: true) {
     59                             scannedSomething = false
     60                             dismissTop(stack.push())
     61                         }
     62                     }
     63                 } else {
     64                     ErrorView(stack.push(),
     65                               title: String(localized: "Scanned QR is no URL"),
     66                             message: scannedCode,
     67                            copyable: true
     68                     ) {
     69                         scannedSomething = false
     70                         dismissTop(stack.push())
     71                     }
     72                 }
     73             } else {
     74                 let codeScannerView = CodeScannerView(codeTypes: [AVMetadataObject.ObjectType.qr],
     75                                                  showViewfinder: true,
     76                                                       isTorchOn: isTorchOn
     77                 ) { response in
     78                     let closingAnnouncement: String
     79                     switch response {
     80                         case .success(let result):
     81                             symLog.log("Found code: \(result.string)")
     82                             scannedSomething = true
     83                             urlToOpen = codeToURL(result.string)
     84                             scannedCode = result.string
     85                             closingAnnouncement = String(localized: "QR code recognized", comment: "a11y")
     86                         case .failure(let error):
     87                             // TODO: errorAlert
     88                             scannedSomething = false
     89                             model.setError(error)
     90                             closingAnnouncement = errorAnnouncement
     91                     }
     92                         announce(closingAnnouncement)
     93                 }
     94 
     95                 if minimalistic {
     96                     codeScannerView
     97                         .onTapGesture { isTorchOn.toggle() }
     98                 } else if #available(iOS 16.4, *) {
     99                     codeScannerView
    100                         .toolbar {
    101                             ToolbarItem(placement: .topBarLeading) {
    102                                 Button {
    103                                     dismissTop(stack.push())
    104                                 } label: {
    105                                     Image(systemName: XMARK)
    106                                 }
    107                                 .accessibilityHidden(true)      // VoiceOver has its own "Dismiss Popup" button
    108                             }
    109                             ToolbarItem(placement: .topBarTrailing) {
    110                                 let a11yValue = isTorchOn ? String(localized: "on", comment: "a11y")
    111                                                           : String(localized: "off", comment: "a11y")
    112                                 Button {
    113                                     isTorchOn.toggle()
    114                                 } label: {
    115                                     Image(systemName: isTorchOn ? LIGHT_ON : LIGHT_OFF)
    116                                 }
    117                                 .accessibilityLabel(Text("Torch for QR code scanning", comment: "a11y"))
    118                                 .accessibilityValue(Text(a11yValue))
    119                             }
    120                         }
    121                         .navigationBarTitleDisplayMode(.inline)
    122                         .toolbarBackground(.gray)
    123                         .toolbarBackground(.visible)
    124                 } else {
    125                     codeScannerView
    126                         .onTapGesture { isTorchOn.toggle() }
    127                 }
    128             }
    129         }
    130     }
    131 }
    132 // MARK: -
    133 //struct PaySheet_Previews: PreviewProvider {
    134 //    static var previews: some View {
    135 //            // needs BackendManager
    136 //        URLSheet(urlToOpen: URL(string: "ftp://this.URL.is.invalid")!)
    137 //    }
    138 //}