commit 73b803bef987abfc6569f37984e61d8b936e7a3c
parent fb56290300837674f772637b5d3609e41fbc84d1
Author: Marc Stibane <marc@taler.net>
Date: Fri, 12 Dec 2025 08:48:32 +0100
refactor for dialog tx
Diffstat:
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 {