taler-ios

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

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 }