taler-ios

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

commit 848ae7c195fa3fdc5b0c9ca035e5f586a4832d11
parent 0596238b64f2d087fcec075b4de88f7b14dcbee9
Author: Marc Stibane <marc@taler.net>
Date:   Mon, 28 Sep 2026 22:24:44 +0200

Error handling w. animations

Diffstat:
MTalerWallet1/Views/Transactions/PaymentTransactionView.swift | 6++++++
MTalerWallet1/Views/Transactions/TransactionSummaryList.swift | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
MTalerWallet1/Views/Transactions/TransactionTypeDetail.swift | 2++
3 files changed, 61 insertions(+), 19 deletions(-)

diff --git a/TalerWallet1/Views/Transactions/PaymentTransactionView.swift b/TalerWallet1/Views/Transactions/PaymentTransactionView.swift @@ -75,6 +75,7 @@ struct PaymentTransactionView: View { let paymentTransaction: PaymentTransaction @Binding var scope: ScopeInfo? @Binding var effective: Amount? + @Binding var possible: Bool? @Binding var payNow: Bool @Binding var selectedChoice: Int? @@ -105,7 +106,10 @@ struct PaymentTransactionView: View { if selectionDetail.status == .paymentPossible { selectedChoice = select effective = selectionDetail.amountEffective + possible = true return select + } else { + possible = false } return nil } @@ -167,9 +171,11 @@ struct PaymentTransactionView: View { if let newValue, newValue < choices.count { let newChoice = choices[newValue] effective = newChoice.0.amountEffective + possible = newChoice.0.status == .paymentPossible scope = newChoice.0.scopeInfo } else { effective = nil + possible = nil scope = nil } } diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryList.swift b/TalerWallet1/Views/Transactions/TransactionSummaryList.swift @@ -57,8 +57,10 @@ struct TransactionSummaryList: View { @State private var viewId = UUID() @State private var selectedChoice: Int? = nil @State private var effective: Amount? = nil + @State private var paymentPossible: Bool? = nil @State private var scope: ScopeInfo? = nil @State private var payNow: Bool = false + @State private var isWorking: Bool = false @Namespace var topID func loadTransaction() async { @@ -67,11 +69,11 @@ struct TransactionSummaryList: View { symLog.log("reloaded \(reloadedTransaction.localizedType): \(reloadedTransaction.common.txState.major)") withAnimation { talerTX = reloadedTransaction; + isWorking = reloadedTransaction.common.isWorking #if DEBUG // controller.playSound(1104, useSystem: true) #endif scope = reloadedTransaction.common.scopes.first - viewId = UUID() // redraw } if developerMode { if let json = try? await model.jsonTransactionById(transactionId, @@ -82,7 +84,11 @@ struct TransactionSummaryList: View { } } } else { - withAnimation{ talerTX = TalerTransaction(dummyCurrency: DEMOCURRENCY); viewId = UUID() } + withAnimation{ + talerTX = TalerTransaction(dummyCurrency: DEMOCURRENCY); + isWorking = false + viewId = UUID() + } jsonTransaction = EMPTYSTRING } } @@ -158,7 +164,11 @@ struct TransactionSummaryList: View { } else { Task { // runs on MainActor // flush the screen first, then reload - withAnimation { talerTX = TalerTransaction(dummyCurrency: DEMOCURRENCY); viewId = UUID() } + withAnimation { + talerTX = TalerTransaction(dummyCurrency: DEMOCURRENCY); + isWorking = false + viewId = UUID() + } if newMajor != .none { // don't reload after delete symLog.log("newState: \(newMajor), reloading transaction, for: \(message)") await loadTransaction() @@ -266,14 +276,20 @@ struct TransactionSummaryList: View { } // Delete button } + var possibleStr: String { + if let possible = paymentPossible { + return possible ? String(localized: "Confirm Payment", comment:"pay merchant navTitle") + : String(localized: "Payment not possible", comment:"pay merchant navTitle") + } + return String(localized: "Payment", comment:"pay merchant navTitle") + } var navTitleStr: String { let isPaying = talerTX.isPayment && talerTX.isDialog let isRepurchase = talerTX.common.isRepurchase return talerTX.isDone ? talerTX.localizedTypePast - : isPaying ? (effective != nil ? String(localized: "Confirm Payment", comment:"pay merchant navTitle") - : String(localized: "Payment not possible", comment:"pay merchant navTitle")) - : isRepurchase ? String(localized: "Already paid", comment:"repurchase merchant navTitle") - : talerTX.localizedType + : isPaying ? possibleStr + : isRepurchase ? String(localized: "Already paid", comment:"repurchase merchant navTitle") + : talerTX.localizedType } var body: some View { @@ -292,12 +308,13 @@ struct TransactionSummaryList: View { if developerMode && withActions { suspendResume(common) } if !isPaying { Section { - VStack(alignment: .leading) { - dateAndStatus(common) - .listRowSeparator(.hidden) - .talerFont(.title) - }.overlay { - if common.isWorking { + HStack { + VStack(alignment: .leading) { + dateAndStatus(common) + .listRowSeparator(.hidden) + .talerFont(.title) + } + if isWorking { #if DEBUG let background = developerMode ? Color.red : nil #else @@ -306,12 +323,13 @@ struct TransactionSummaryList: View { RotatingTaler(size: 80, progress: true, once: false, background: background, rotationEnabled: Binding.constant(true)) + .transition(.opacity.combined(with: .scale)) } } } } if controller.stalledConnection { - StalledView() +// StalledView() => show this in the safeArea } else { TransactionTypeDetail(stack: stack.push(), transaction: $talerTX, @@ -319,6 +337,7 @@ struct TransactionSummaryList: View { selectedChoice: $selectedChoice, scope: $scope, effective: $effective, // return amount for "Pay <amount> now" + possible: $paymentPossible, hasDone: hasDone) // TODO: Retry Countdown, Retry Now button @@ -334,6 +353,9 @@ struct TransactionSummaryList: View { .navigationBarBackButtonHidden(hasDone) .interactiveDismissDisabled(hasDone && !isPaying) // can only use "Done" button to dismiss .safeAreaInset(edge: .bottom) { + let cancelButton = Button("Cancel") { dismissTop(stack.push()) } + .buttonStyle(TalerButtonStyle(type: .bordered)) + .padding(is26 ? .horizontal : .all) let bottomButton = ZStack { if isPaying, case .payment(let paymentTransaction) = talerTX { let details = paymentTransaction.details @@ -346,9 +368,7 @@ struct TransactionSummaryList: View { amountA11y: formatted.1, payNow: $payNow) } else { - Button("Cancel") { dismissTop(stack.push()) } - .buttonStyle(TalerButtonStyle(type: .bordered)) - .padding(is26 ? .horizontal : .all) + cancelButton } // Cancel } else if let showDone { Button("Done") { dismissTop(stack.push()) } @@ -356,8 +376,22 @@ struct TransactionSummaryList: View { .padding(is26 ? .horizontal : .all) } } - bottomButton - .padding(is26 ? .horizontal : .all) + if controller.stalledConnection { + VStack { + StalledView() + cancelButton + .padding(.bottom) + } + .background { + SelectedBackground(useColor: false) + } + .padding(is26 ? .horizontal : .all) + .transition(.move(edge: .bottom)) + } else { + bottomButton + .padding(is26 ? .horizontal : .all) + .transition(.scale.combined(with: .opacity)) + } } .navigationTitle(navTitle ?? navTitleStr) .onChange(of: payNow) { payNow2 in diff --git a/TalerWallet1/Views/Transactions/TransactionTypeDetail.swift b/TalerWallet1/Views/Transactions/TransactionTypeDetail.swift @@ -18,6 +18,7 @@ struct TransactionTypeDetail: View { @Binding var selectedChoice: Int? @Binding var scope: ScopeInfo? @Binding var effective: Amount? + @Binding var possible: Bool? let hasDone: Bool @EnvironmentObject private var controller: Controller @@ -146,6 +147,7 @@ struct TransactionTypeDetail: View { paymentTransaction: paymentTransaction, scope: $scope, effective: $effective, + possible: $possible, payNow: $payNow, selectedChoice: $selectedChoice) .task(id: common.txState.hashValue) {