taler-ios

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

commit 73b803bef987abfc6569f37984e61d8b936e7a3c
parent fb56290300837674f772637b5d3609e41fbc84d1
Author: Marc Stibane <marc@taler.net>
Date:   Fri, 12 Dec 2025 08:48:32 +0100

refactor for dialog tx

Diffstat:
MTalerWallet1/Views/Sheets/Payment/PaymentView.swift | 274+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
MTalerWallet1/Views/Transactions/TransactionSummaryV.swift | 49+++++++++++++++++++++++++++++++++++++++----------
2 files changed, 202 insertions(+), 121 deletions(-)

diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift @@ -142,30 +142,6 @@ struct PaymentView: View, Sendable { } } - func timeToPay(_ terms: MerchantContractTerms) -> Int { - if let milliseconds = try? terms.payDeadline.milliseconds() { - let date = Date(milliseconds: milliseconds) - let now = Date.now - let timeInterval = now.timeIntervalSince(date) - if timeInterval < 0 { - symLog.log("\(timeInterval) seconds left to pay") - return Int(-timeInterval) - } else { - symLog.log("\(date) - \(now) = \(timeInterval)") - } - } else { - symLog.log("no milliseconds") - } - return 0 - } - - func computeFee(raw: Amount?, eff: Amount?) -> Amount? { - if let raw, let eff { - return try! Amount.diff(raw, eff) // TODO: different currencies - } - return nil - } - var body: some View { Group { if let preparePayResult { @@ -173,6 +149,7 @@ struct PaymentView: View, Sendable { let scopes = preparePayResult.scopes // TODO: might be nil let firstScope = scopes?.first let raw = preparePayResult.amountRaw +// let currency = raw?.currencyStr ?? UNKNOWN // TODO: v1 has currencies buried in choices let currency = raw.currencyStr let effective = preparePayResult.amountEffective let terms = preparePayResult.contractTerms @@ -181,8 +158,6 @@ struct PaymentView: View, Sendable { let paid = status == .alreadyConfirmed let navTitle = paid ? String(localized: "Already paid", comment:"pay merchant navTitle") : String(localized: "Confirm Payment", comment:"pay merchant navTitle") - let timeToPay = timeToPay(terms) - List { if paid { Text("You already paid for this article.") @@ -196,6 +171,91 @@ struct PaymentView: View, Sendable { } } } + + PaymentView2(stack: stack.push(), + paid: paid, + raw: raw, + effective: effective, + firstScope: firstScope, + baseURL: baseURL, +// terms: terms, + summary: terms.summary, + merchant: terms.merchant.name, + products: terms.products, + balanceDetails: preparePayResult.balanceDetails) + + } + .listStyle(myListStyle.style).anyView + .safeAreaInset(edge: .bottom) { + if !paid { + if let effective { + PaySafeArea(symLog: symLog, + stack: stack.push(), + terms: terms, + url: url, + effective: effective, + transactionId: preparePayResult.transactionId, + currencyInfo: $currencyInfo) + } else { + Button("Cancel") { + dismissTop(stack.push()) + } + .buttonStyle(TalerButtonStyle(type: .bordered)) + .padding(.horizontal) + } // Cancel + } + } + .navigationTitle(navTitle) + .task(id: controller.currencyTicker) { + let currency = amountToTransfer.currencyStr + let scopes = preparePayResult.scopes // TODO: might be nil + if let resultScope = scopes?.first { // TODO: let user choose which currency + currencyInfo = controller.info(for: resultScope, controller.currencyTicker) + } else { + currencyInfo = controller.info2(for: currency, controller.currencyTicker) + } + symLog.log("Info(for: \(currency)) loaded: \(currencyInfo.name)") + } +#if OIM + .overlay { if #available(iOS 16.4, *) { + if controller.oimSheetActive { + OIMpayView(stack: stack.push(), + amount: effective) + } + } } +#endif + } else { + LoadingView(stack: stack.push(), scopeInfo: nil, message: url.host) + .task { await viewDidLoad() } + } + }.onAppear() { + symLog.log("onAppear") + DebugViewC.shared.setSheetID(SHEET_PAYMENT) + } + } +} +// MARK: - +struct PaymentView2: View, Sendable { + let stack: CallStack + let paid: Bool + let raw: Amount + let effective: Amount? + let firstScope: ScopeInfo? + let baseURL: String? +// let terms: MerchantContractTerms + let summary: String? + let merchant: String? + let products: [Product]? + let balanceDetails: PayMerchantInsufficientBalanceDetails? + + func computeFee(raw: Amount?, eff: Amount?) -> Amount? { + if let raw, let eff { + return try! Amount.diff(raw, eff) // TODO: different currencies + } + return nil + } + + var body: some View { // TODO: show balanceDetails.balanceAvailable let topTitle = paid ? String(localized: "Paid amount:") : String(localized: "Amount to pay:") @@ -221,12 +281,12 @@ struct PaymentView: View, Sendable { incoming: false, baseURL: baseURL, txStateLcl: nil, - summary: terms.summary, - merchant: terms.merchant.name, - products: terms.products) + summary: summary, + merchant: merchant, + products: products) // TODO: payment: popup with all possible exchanges, check fees - } else if let balanceDetails = preparePayResult.balanceDetails { // Insufficient - let localizedCause = balanceDetails.causeHint.localizedCause(currency) + } else if let balanceDetails { // Insufficient + let localizedCause = balanceDetails.causeHint.localizedCause(raw.currencyStr) Text(localizedCause) .talerFont(.headline) ThreeAmountsSection(stack: stack.push(), @@ -245,96 +305,88 @@ struct PaymentView: View, Sendable { incoming: false, baseURL: baseURL, txStateLcl: nil, - summary: terms.summary, - merchant: terms.merchant.name, - products: terms.products) + summary: summary, + merchant: merchant, + products: products) } else { // TODO: Error - neither effective nor balanceDetails Text("Error") .talerFont(.body) } + + } +} +// MARK: - +struct PaySafeArea: View, Sendable { + let symLog: SymLogV? + let stack: CallStack + let terms: MerchantContractTerms + // the scanned URL + let url: URL + let effective: Amount + let transactionId: String + @Binding var currencyInfo: CurrencyInfo + + func timeToPay(_ terms: MerchantContractTerms) -> Int { + if let milliseconds = try? terms.payDeadline.milliseconds() { + let date = Date(milliseconds: milliseconds) + let now = Date.now + let timeInterval = now.timeIntervalSince(date) + if timeInterval < 0 { + symLog?.log("\(timeInterval) seconds left to pay") + return Int(-timeInterval) + } else { + symLog?.log("\(date) - \(now) = \(timeInterval)") } - .listStyle(myListStyle.style).anyView - .safeAreaInset(edge: .bottom) { - if !paid { - if let effective { - VStack { - if timeToPay > 0 && timeToPay < 300 { - let startDate = Date() - HStack { - Text("Time to pay:") - TimelineView(.animation) { context in - let elapsed = Int(context.date.timeIntervalSince(startDate)) - let seconds = timeToPay - elapsed - let text = Text(verbatim: "\(seconds)") - if #available(iOS 17.0, *) { - text - .contentTransition(.numericText(countsDown: true)) - .animation(.default, value: elapsed) - } else if #available(iOS 16.4, *) { - text - .animation(.default, value: elapsed) - } else { - text - } - }.monospacedDigit() - Text("seconds") - }.accessibilityElement(children: .combine) - } else { - let _ = symLog.log("\(timeToPay) not shown") - } - let destination = PaymentDone(stack: stack.push(), - url: url, -// scope: firstScope, // TODO: let user choose which currency - transactionId: preparePayResult.transactionId) - NavigationLink(destination: destination) { - let formatted = effective.formatted(currencyInfo, isNegative: false) - Text("Pay \(formatted.0) now") - .accessibilityLabel(Text("Pay \(formatted.1) now", comment: "a11y")) - } - .buttonStyle(TalerButtonStyle(type: .prominent)) - .padding(.horizontal) - let currency = currencyInfo.currency -// let currency = amountToTransfer.currencyStr - Text("Payment is made in \(currency)") - .talerFont(.callout) - } - } else { - Button("Cancel") { - dismissTop(stack.push()) + } else { + symLog?.log("no milliseconds") + } + return 0 + } + + var body: some View { + let timeToPay = timeToPay(terms) + VStack { + if timeToPay > 0 && timeToPay < 300 { + let startDate = Date() + HStack { + Text("Time to pay:") + TimelineView(.animation) { context in + let elapsed = Int(context.date.timeIntervalSince(startDate)) + let seconds = timeToPay - elapsed + let text = Text(verbatim: "\(seconds)") + if #available(iOS 17.0, *) { + text + .contentTransition(.numericText(countsDown: true)) + .animation(.default, value: elapsed) + } else if #available(iOS 16.4, *) { + text + .animation(.default, value: elapsed) + } else { + text } - .buttonStyle(TalerButtonStyle(type: .bordered)) - .padding(.horizontal) - } // Cancel - } + }.monospacedDigit() + Text("seconds") + }.accessibilityElement(children: .combine) + } else { + let _ = symLog?.log("\(timeToPay) not shown") } - .navigationTitle(navTitle) - .task(id: controller.currencyTicker) { - let currency = amountToTransfer.currencyStr - let scopes = preparePayResult.scopes // TODO: might be nil - if let resultScope = scopes?.first { // TODO: let user choose which currency - currencyInfo = controller.info(for: resultScope, controller.currencyTicker) - } else { - currencyInfo = controller.info2(for: currency, controller.currencyTicker) - } - symLog.log("Info(for: \(currency)) loaded: \(currencyInfo.name)") + let destination = PaymentDone(stack: stack.push(), + url: url, +// scope: firstScope, // TODO: let user choose which currency + transactionId: transactionId) + NavigationLink(destination: destination) { + let formatted = effective.formatted(currencyInfo, isNegative: false) + Text("Pay \(formatted.0) now") + .accessibilityLabel(Text("Pay \(formatted.1) now", comment: "a11y")) } -#if OIM - .overlay { if #available(iOS 16.4, *) { - if controller.oimSheetActive { - OIMpayView(stack: stack.push(), - amount: effective) - } - } } -#endif - } else { - LoadingView(stack: stack.push(), scopeInfo: nil, message: url.host) - .task { await viewDidLoad() } + .buttonStyle(TalerButtonStyle(type: .prominent)) + .padding(.horizontal) + let currency = currencyInfo.currency +// let currency = amountToTransfer.currencyStr + Text("Payment is made in \(currency)") + .talerFont(.callout) } - }.onAppear() { - symLog.log("onAppear") - DebugViewC.shared.setSheetID(SHEET_PAYMENT) - } } } // MARK: - diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift @@ -441,6 +441,7 @@ struct TransactionSummaryV: View { @Environment(\.colorSchemeContrast) private var colorSchemeContrast @AppStorage("minimalistic") var minimalistic: Bool = false @State private var rotationEnabled = true + @State private var ignoreAccept: Bool = false // accept could be set by OIM to trigger accept func refreshFee(input: Amount, output: Amount) -> Amount? { do { @@ -468,6 +469,7 @@ struct TransactionSummaryV: View { var body: some View { let common = transaction.common let pending = transaction.isPending + let dialog = transaction.isDialog Group { switch transaction { case .dummy(_): @@ -522,8 +524,22 @@ struct TransactionSummaryV: View { } case .payment(let paymentTransaction): Group { let details = paymentTransaction.details - TransactionPayDetailV(paymentTx: paymentTransaction) - ThreeAmountsSheet(stack: stack.push(), + if common.isDialog { + let firstScope = common.scopes.first + PaymentView2(stack: stack.push(), + paid: false, + raw: common.amountRaw, + effective: common.amountEffective, + firstScope: firstScope, + baseURL: nil, + summary: details.info.summary, + merchant: details.info.merchant.name, + products: details.info.products, + balanceDetails: nil) + + } else { + TransactionPayDetailV(paymentTx: paymentTransaction) + ThreeAmountsSheet(stack: stack.push(), scope: scope, common: common, topAbbrev: String(localized: "Price:", comment: "mini"), @@ -534,6 +550,7 @@ struct TransactionSummaryV: View { large: true, summary: details.info.summary, merchant: details.info.merchant.name) + } } case .refund(let refundTransaction): Group { let details = refundTransaction.details // TODO: more details @@ -603,15 +620,26 @@ struct TransactionSummaryV: View { KYCbutton(kycUrl: common.kycUrl) } if !transaction.isDone { - let expiration = details.info.expiration - let (dateString, date) = TalerDater.dateString(expiration, minimalistic) - let a11yDate = TalerDater.accessibilityDate(date) ?? dateString - let a11yLabel = String(localized: "Expires: \(a11yDate)", comment: "a11y") - Text("Expires: \(dateString)") - .talerFont(.body) - .accessibilityLabel(a11yLabel) - .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) + ExpiresView(expiration: details.info.expiration) } + if transaction.isRcvCoins && common.isDialog { + PeerPushCreditView(stack: stack.push(), + raw: common.amountRaw, + effective: common.amountEffective, + scope: scope, + summary: details.info.summary) + PeerPushCreditAccept(stack: stack.push(), url: nil, + transactionId: common.transactionId, + accept: $ignoreAccept) + } else if transaction.isPayInvoice && common.isDialog { + PeerPullDebitView(stack: stack.push(), + raw: common.amountRaw, + effective: common.amountEffective, + scope: scope, + summary: details.info.summary) + PeerPullDebitConfirm(stack: stack.push(), url: nil, + transactionId: common.transactionId) + } else { // TODO: isSendCoins should show QR only while not yet expired - either set timer or wallet-core should do so and send a state-changed notification if pending { if transaction.isPendingReady { @@ -642,6 +670,7 @@ struct TransactionSummaryV: View { large: false, summary: details.info.summary, merchant: nil) + } // else } // p2p case .recoup(let recoupTransaction): Group {