taler-ios

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

commit 367047f3dfae1d35fa8c1e56e884de917225eff7
parent 4ca2ef32cf92cbc9005e4ce30ebc726ae65365a9
Author: Marc Stibane <marc@taler.net>
Date:   Wed, 26 Jun 2024 15:01:31 +0200

move QR scanner into MainView

Diffstat:
MTalerWallet1/Views/Balances/BalancesListView.swift | 158+++++++++++++++++--------------------------------------------------------------
MTalerWallet1/Views/Balances/BalancesSectionView.swift | 12+++++++++---
MTalerWallet1/Views/Main/MainView.swift | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
MTalerWallet1/Views/Peer2peer/RequestPayment.swift | 2++
MTalerWallet1/Views/Peer2peer/SendAmount.swift | 10+++++++++-
5 files changed, 131 insertions(+), 134 deletions(-)

diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift b/TalerWallet1/Views/Balances/BalancesListView.swift @@ -11,72 +11,18 @@ import AVFoundation struct BalancesListView: View { private let symLog = SymLogV(0) let stack: CallStack - let navTitle: String @Binding var balances: [Balance] // @Binding var shouldReloadPending: Int @Binding var shouldReloadBalances: Int + let cameraAction: () -> Void @EnvironmentObject private var model: WalletModel @EnvironmentObject private var controller: Controller + @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic @State private var lastReloadedBalances = 0 @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used @State private var summary: String = "" - @State private var showQRScanner: Bool = false - @State private var showCameraAlert: Bool = false - - private var openSettingsButton: some View { - Button("Open Settings") { - showCameraAlert = false - UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) - } - } - var ClosingAnnouncement = AttributedString(localized: "Closing Camera") - private var dismissAlertButton: some View { - Button("Cancel", role: .cancel) { - if #available(iOS 17.0, *) { - AccessibilityNotification.Announcement(ClosingAnnouncement).post() - } - showCameraAlert = false - } - } - private func dismissingSheet() { - if #available(iOS 17.0, *) { - AccessibilityNotification.Announcement(ClosingAnnouncement).post() - } - } - - var defaultPriorityAnnouncement = AttributedString(localized: "Opening Camera") - var lowPriorityAnnouncement: AttributedString { - var lowPriorityString = AttributedString ("Camera Loading") - if #available(iOS 17.0, *) { - lowPriorityString.accessibilitySpeechAnnouncementPriority = .low - } - return lowPriorityString - } - var highPriorityAnnouncement: AttributedString { - var highPriorityString = AttributedString("Camera Active") - if #available(iOS 17.0, *) { - highPriorityString.accessibilitySpeechAnnouncementPriority = .high - } - return highPriorityString - } - private func checkCameraAvailable() -> Void { - // Open Camera when QR-Button was tapped - if #available(iOS 17.0, *) { - AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post() - } - AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) -> Void in - if granted { - showQRScanner = true // present sheet - if #available(iOS 17.0, *) { - AccessibilityNotification.Announcement(highPriorityAnnouncement).post() - } - } else { - showCameraAlert = true - } - }) - } /// runs on MainActor if called in some Task {} @discardableResult @@ -99,77 +45,41 @@ struct BalancesListView: View { let _ = Self._printChanges() let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear #endif - Content(symLog: symLog, stack: stack.push(), balances: $balances, - amountToTransfer: $amountToTransfer, summary: $summary, -// shouldReloadPending: $shouldReloadPending, - shouldReloadBalances: $shouldReloadBalances, - reloadBalances: reloadBalances) - .navigationTitle(navTitle) - .navigationBarItems(trailing: QRButton(action: checkCameraAvailable)) - .alert("Scanning QR-codes requires access to the camera", - isPresented: $showCameraAlert, - actions: { openSettingsButton - dismissAlertButton }, - message: { Text("Please allow camera access in settings.") }) - .sheet(isPresented: $showQRScanner, onDismiss: dismissingSheet) { - let sheet = AnyView(QRSheet(stack: stack.push(".sheet"))) - Sheet(sheetView: sheet) - } // sheet - .task(id: shouldReloadBalances) { - symLog.log(".task shouldReloadBalances \(shouldReloadBalances)") - let invalidateCache = (lastReloadedBalances != shouldReloadBalances) - lastReloadedBalances = shouldReloadBalances - await reloadBalances(stack.push(".task"), invalidateCache) - } // task - } -} -// MARK: - -extension BalancesListView { - struct Content: View { - let symLog: SymLogV? - let stack: CallStack - @Binding var balances: [Balance] - @Binding var amountToTransfer: Amount - @Binding var summary: String -// @Binding var shouldReloadPending: Int - @Binding var shouldReloadBalances: Int - var reloadBalances: (_ stack: CallStack, _ invalidateCache: Bool) async -> Int? - - @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic - - var body: some View { -#if PRINT_CHANGES - let _ = Self._printChanges() - let _ = symLog?.vlog() // just to get the # to compare it with .onAppear & onDisappear -#endif - Group { // necessary for .backslide transition (bug in SwiftUI) - let count = balances.count - if balances.isEmpty { - WalletEmptyView(stack: stack.push("isEmpty")) - } else { - List(balances, id: \.self) { balance in - BalancesSectionView(stack: stack.push("\(balance.scopeInfo.currency)"), - balance: balance, // this is the currency to be used - sectionCount: count, - amountToTransfer: $amountToTransfer, // does still have the wrong currency - summary: $summary, - shouldReloadBalances: $shouldReloadBalances) - } - .onAppear() { - DebugViewC.shared.setViewID(VIEW_BALANCES, stack: stack.push("onAppear")) - } - .listStyle(myListStyle.style).anyView + Group { // necessary for .backslide transition (bug in SwiftUI) + let count = balances.count + if balances.isEmpty { + WalletEmptyView(stack: stack.push("isEmpty")) + } else { + List(balances, id: \.self) { balance in + BalancesSectionView(stack: stack.push("\(balance.scopeInfo.currency)"), + balance: balance, // this is the currency to be used + sectionCount: count, + amountToTransfer: $amountToTransfer, // does still have the wrong currency + summary: $summary, + shouldReloadBalances: $shouldReloadBalances, + cameraAction: cameraAction) } + .onAppear() { + DebugViewC.shared.setViewID(VIEW_BALANCES, stack: stack.push("onAppear")) + } + .listStyle(myListStyle.style).anyView } + } #if REFRESHABLE - .refreshable { // already async - symLog?.log("refreshing balances") - let count = await reloadBalances(stack.push("refreshing balances"), true) - if let count, count > 0 { - NotificationCenter.default.post(name: .BalanceReloaded, object: nil) - } + .refreshable { // already async + symLog?.log("refreshing balances") + let count = await reloadBalances(stack.push("refreshing balances"), true) + if let count, count > 0 { + NotificationCenter.default.post(name: .BalanceReloaded, object: nil) } + } #endif - } // body - } // Content + .navigationBarItems(trailing: QRButton(action: cameraAction)) + .task(id: shouldReloadBalances) { + symLog.log(".task shouldReloadBalances \(shouldReloadBalances)") + let invalidateCache = (lastReloadedBalances != shouldReloadBalances) + lastReloadedBalances = shouldReloadBalances + await reloadBalances(stack.push(".task"), invalidateCache) + } // task + } } diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift b/TalerWallet1/Views/Balances/BalancesSectionView.swift @@ -22,6 +22,7 @@ struct BalancesSectionView { @Binding var amountToTransfer: Amount // does still have the wrong currency @Binding var summary: String @Binding var shouldReloadBalances: Int + let cameraAction: () -> Void @EnvironmentObject private var model: WalletModel @Environment(\.colorScheme) private var colorScheme @@ -84,7 +85,8 @@ extension BalancesSectionView: View { summary: $summary, completedTransactions: $completedTransactions, reloadAllAction: reloadCompleted, - reloadOneAction: reloadOneAction) + reloadOneAction: reloadOneAction, + cameraAction: cameraAction) if pendingTransactions.count > 0 { BalancesPendingRowView(symLog: symLog, stack: stack.push(), @@ -233,6 +235,8 @@ fileprivate struct BalancesNavigationLinksView: View { @Binding var completedTransactions: [Transaction] let reloadAllAction: (_ stack: CallStack) async -> () let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async throws -> Transaction) + let cameraAction: () -> Void + // @EnvironmentObject private var model: WalletModel @State private var buttonSelected: Int? = nil @@ -261,7 +265,8 @@ fileprivate struct BalancesNavigationLinksView: View { amountAvailable: balance.available, amountToTransfer: $amountToTransfer, // with correct currency summary: $summary, - scopeInfo: balance.scopeInfo) + scopeInfo: balance.scopeInfo, + cameraAction: cameraAction) }, tag: 1, selection: $buttonSelected ) { EmptyView() }.frame(width: 0).opacity(0).hidden() // SendAmount @@ -269,7 +274,8 @@ fileprivate struct BalancesNavigationLinksView: View { RequestPayment(stack: stack.push(), amountToTransfer: $amountToTransfer, // with correct currency summary: $summary, - scopeInfo: balance.scopeInfo) + scopeInfo: balance.scopeInfo, + cameraAction: cameraAction) }, tag: 2, selection: $buttonSelected ) { EmptyView() }.frame(width: 0).opacity(0).hidden() // RequestPayment diff --git a/TalerWallet1/Views/Main/MainView.swift b/TalerWallet1/Views/Main/MainView.swift @@ -9,6 +9,7 @@ import SwiftUI import os.log import SymLog +import AVFoundation // Use this to delay instantiation when using `NavigationLink`, etc... struct LazyView<Content: View>: View { @@ -38,11 +39,67 @@ struct MainView: View { @State private var sheetPresented = false @State private var urlToOpen: URL? = nil + @State private var showQRScanner: Bool = false + @State private var showCameraAlert: Bool = false func sheetDismissed() -> Void { logger.info("sheet dismiss") ViewState.shared.popToRootView(nil) } + + private var openSettingsButton: some View { + Button("Open Settings") { + showCameraAlert = false + UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) + } + } + let ClosingAnnouncement = AttributedString(localized: "Closing Camera") + private var dismissAlertButton: some View { + Button("Cancel", role: .cancel) { + if #available(iOS 17.0, *) { + AccessibilityNotification.Announcement(ClosingAnnouncement).post() + } + showCameraAlert = false + } + } + private func dismissingSheet() { + if #available(iOS 17.0, *) { + AccessibilityNotification.Announcement(ClosingAnnouncement).post() + } + } + + var defaultPriorityAnnouncement = AttributedString(localized: "Opening Camera") + var lowPriorityAnnouncement: AttributedString { + var lowPriorityString = AttributedString ("Camera Loading") + if #available(iOS 17.0, *) { + lowPriorityString.accessibilitySpeechAnnouncementPriority = .low + } + return lowPriorityString + } + var highPriorityAnnouncement: AttributedString { + var highPriorityString = AttributedString("Camera Active") + if #available(iOS 17.0, *) { + highPriorityString.accessibilitySpeechAnnouncementPriority = .high + } + return highPriorityString + } + private func checkCameraAvailable() -> Void { + // Open Camera when QR-Button was tapped + if #available(iOS 17.0, *) { + AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post() + } + AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) -> Void in + if granted { + showQRScanner = true // present sheet + if #available(iOS 17.0, *) { + AccessibilityNotification.Announcement(highPriorityAnnouncement).post() + } + } else { + showCameraAlert = true + } + }) + } + var body: some View { #if PRINT_CHANGES let _ = Self._printChanges() @@ -50,7 +107,9 @@ struct MainView: View { #endif Group { if controller.backendState == .ready { - Content(logger: logger, stack: stack.push("Content"), talerFontIndex: $talerFontIndex) + Content(logger: logger, stack: stack.push("Content"), + talerFontIndex: $talerFontIndex, + cameraAction: checkCameraAvailable) .onAppear() { #if DEBUG if playSoundsI != 0 && playSoundsB && !soundPlayed { @@ -59,6 +118,15 @@ struct MainView: View { #endif soundPlayed = true } + .alert("Scanning QR-codes requires access to the camera", + isPresented: $showCameraAlert, + actions: { openSettingsButton + dismissAlertButton }, + message: { Text("Please allow camera access in settings.") }) + .sheet(isPresented: $showQRScanner, onDismiss: dismissingSheet) { + let sheet = AnyView(QRSheet(stack: stack.push(".sheet"))) + Sheet(sheetView: sheet) + } // sheet } else if controller.backendState == .error { ErrorView(errortext: nil) // TODO: show Error View } else { @@ -106,6 +174,7 @@ extension MainView { let logger: Logger let stack: CallStack @Binding var talerFontIndex: Int + let cameraAction: () -> Void #if DEBUG @AppStorage("developerMode") var developerMode: Bool = true @@ -190,10 +259,11 @@ extension MainView { TabView(selection: tabSelection()) { NavigationView { BalancesListView(stack: stack.push(balancesTitle), - navTitle: balancesTitle, balances: $balances, // shouldReloadPending: $shouldReloadPending, - shouldReloadBalances: $shouldReloadBalances) + shouldReloadBalances: $shouldReloadBalances, + cameraAction: cameraAction) + .navigationTitle(balancesTitle) }.id(viewState.rootViewId) // any change to rootViewId triggers popToRootView behaviour .navigationViewStyle(.stack) .tabItem { @@ -206,10 +276,11 @@ extension MainView { if balances.count > 1 { NavigationView { OverviewListV(stack: stack.push(overviewTitle), - navTitle: overviewTitle, - balances: $balances, + balances: $balances, // shouldReloadPending: $shouldReloadPending, - shouldReloadBalances: $shouldReloadBalances) + shouldReloadBalances: $shouldReloadBalances, + cameraAction: cameraAction) + .navigationTitle(overviewTitle) }.id(viewState2.rootViewId) // any change to rootViewId triggers popToRootView behaviour .navigationViewStyle(.stack) .tabItem { diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Peer2peer/RequestPayment.swift @@ -14,6 +14,7 @@ struct RequestPayment: View { @Binding var amountToTransfer: Amount @Binding var summary: String let scopeInfo: ScopeInfo + let cameraAction: () -> Void @EnvironmentObject private var controller: Controller @EnvironmentObject private var model: WalletModel @@ -102,6 +103,7 @@ struct RequestPayment: View { .onDisappear { symLog.log("❗️ \(navTitle) onDisappear") } + .navigationBarItems(trailing: QRButton(action: cameraAction)) .task(id: amountToTransfer.value) { if exchange == nil { if let url = scopeInfo.url { diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift b/TalerWallet1/Views/Peer2peer/SendAmount.swift @@ -18,6 +18,7 @@ struct SendAmount: View { @Binding var amountToTransfer: Amount @Binding var summary: String let scopeInfo: ScopeInfo + let cameraAction: () -> Void @EnvironmentObject private var controller: Controller @EnvironmentObject private var model: WalletModel @@ -108,6 +109,7 @@ struct SendAmount: View { AmountInputV(stack: stack.push(), url: nil, amountAvailable: amountAvailable, amountToTransfer: $amountToTransfer, + wireFee: nil, amountLabel: amountLabel, summaryIsEditable: true, summary: $summary, @@ -133,6 +135,7 @@ struct SendAmount: View { .onDisappear { symLog.log("❗️ \(navTitle) onDisappear") } + .navigationBarItems(trailing: QRButton(action: cameraAction)) .task(id: amountToTransfer.value) { if exchange == nil { if let url = scopeInfo.url { @@ -176,6 +179,9 @@ fileprivate struct Preview_Content: View { @State private var amountToPreview = Amount(currency: LONGCURRENCY, cent: 510) @State private var summary: String = "" + private func checkCameraAvailable() -> Void { + // Open Camera when QR-Button was tapped + } var body: some View { let amount = Amount(currency: LONGCURRENCY, cent: 1000) let scopeInfo = ScopeInfo(type: .exchange, currency: LONGCURRENCY) @@ -190,7 +196,9 @@ fileprivate struct Preview_Content: View { SendAmount(stack: CallStack("Preview"), amountAvailable: amount, amountToTransfer: $amountToPreview, - summary: $summary, scopeInfo: scopeInfo) + summary: $summary, + scopeInfo: scopeInfo, + cameraAction: checkCameraAvailable) } }