taler-ios

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

commit baa2344ebbf76c1be1ddfd079ea57451041ee66e
parent 0f2ad6c051314389caa7fe9c3fe5f402f29d8f3d
Author: Marc Stibane <marc@taler.net>
Date:   Thu,  7 Nov 2024 21:35:48 +0100

TransactionCommon, row

Diffstat:
MTalerWallet1/Model/Transaction.swift | 22+++++++++++++++++-----
MTalerWallet1/Model/WalletModel.swift | 6++++++
MTalerWallet1/Views/Transactions/ManualDetailsV.swift | 8++++----
MTalerWallet1/Views/Transactions/ManualDetailsWireV.swift | 8++++----
MTalerWallet1/Views/Transactions/ThreeAmountsSection.swift | 7+++----
MTalerWallet1/Views/Transactions/TransactionRowView.swift | 66+++++++++++++++++++++++++++++++++++++++++++++---------------------
MTalerWallet1/Views/Transactions/TransactionSummaryV.swift | 10++++++----
7 files changed, 85 insertions(+), 42 deletions(-)

diff --git a/TalerWallet1/Model/Transaction.swift b/TalerWallet1/Model/Transaction.swift @@ -137,6 +137,9 @@ enum TransactionMajorState: String, Codable { struct TransactionState: Codable, Hashable { var major: TransactionMajorState var minor: TransactionMinorState? + + var isReady: Bool { minor == .ready } + var isKYC: Bool { minor == .kyc || minor == .balanceKyc || minor == .mergeKycRequired } } struct TransactionTransition: Codable { // Notification @@ -284,20 +287,29 @@ enum TransactionType: String, Codable { } } +struct KycAuthTransferInfo: Decodable, Sendable { + /// The KYC auth transfer will *not* work if it originates from a different account. + var debitPaytoUri: String /// Payto URI of the account that must make the transfer + var accountPub: String /// Account public key that must be included in the subject + var creditPaytoUris: [String] /// Possible target payto URIs +} + struct TransactionCommon: Decodable, Sendable { var type: TransactionType - var txState: TransactionState - var amountEffective: Amount - var amountRaw: Amount var transactionId: String var timestamp: Timestamp var scopes: [ScopeInfo] + var txState: TransactionState var txActions: [TxAction] + var amountRaw: Amount + var amountEffective: Amount + var error: TalerErrorDetail? var kycUrl: String? + var kycAuthTransferInfo: KycAuthTransferInfo? var isPending : Bool { txState.major == .pending } - var isPendingReady : Bool { isPending && txState.minor == .ready } - var isPendingKYC : Bool { isPending && (txState.minor == .kyc || txState.minor == .balanceKyc || txState.minor == .mergeKycRequired) } + var isPendingReady : Bool { isPending && txState.isReady } + var isPendingKYC : Bool { isPending && txState.isKYC } var isDone : Bool { txState.major == .done } var isAborting : Bool { txState.major == .aborting } var isAborted : Bool { txState.major == .aborted } diff --git a/TalerWallet1/Model/WalletModel.swift b/TalerWallet1/Model/WalletModel.swift @@ -10,6 +10,12 @@ import os.log fileprivate let DATABASE = "talerwalletdb-v30" fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging +struct TalerErrorDetail: Codable, Hashable { + var code: Int + var when: Timestamp? + var hint: String? +} + struct HTTPError: Codable, Hashable { var code: Int var requestUrl: String? diff --git a/TalerWallet1/Views/Transactions/ManualDetailsV.swift b/TalerWallet1/Views/Transactions/ManualDetailsV.swift @@ -251,13 +251,13 @@ struct ManualDetailsV: View { struct ManualDetails_Previews: PreviewProvider { static var previews: some View { let common = TransactionCommon(type: .withdrawal, - txState: TransactionState(major: .done), - amountEffective: Amount(currency: LONGCURRENCY, cent: 110), - amountRaw: Amount(currency: LONGCURRENCY, cent: 220), transactionId: "someTxID", timestamp: Timestamp(from: 1_666_666_000_000), scopes: [], - txActions: []) + txState: TransactionState(major: .done), + txActions: [], + amountRaw: Amount(currency: LONGCURRENCY, cent: 220), + amountEffective: Amount(currency: LONGCURRENCY, cent: 110)) let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company" let details = WithdrawalDetails(type: .manual, reservePub: "ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl", diff --git a/TalerWallet1/Views/Transactions/ManualDetailsWireV.swift b/TalerWallet1/Views/Transactions/ManualDetailsWireV.swift @@ -173,14 +173,14 @@ struct ManualDetailsWireV: View { //struct ManualDetailsWire_Previews: PreviewProvider { // static var previews: some View { // let common = TransactionCommon(type: .withdrawal, -// txState: TransactionState(major: .done), -// amountEffective: Amount(currency: LONGCURRENCY, cent: 110), -// amountRaw: Amount(currency: LONGCURRENCY, cent: 220), // transactionId: "someTxID", // timestamp: Timestamp(from: 1_666_666_000_000), +// txState: TransactionState(major: .done), // txActions: []) +// amountEffective: Amount(currency: LONGCURRENCY, cent: 110), +// amountRaw: Amount(currency: LONGCURRENCY, cent: 220), // let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company" -// let details = WithdrawalDetails(type: .manual, +// let details = WithdrawalDetails(type: .manual, // reservePub: "ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl", // reserveIsReady: false, // confirmed: false) diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift b/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift @@ -182,14 +182,13 @@ struct ThreeAmounts_Previews: PreviewProvider { var body: some View { let scope = ScopeInfo.zero(LONGCURRENCY) let common = TransactionCommon(type: .withdrawal, - txState: TransactionState(major: .done), - amountEffective: Amount(currency: LONGCURRENCY, cent: 10), - amountRaw: Amount(currency: LONGCURRENCY, cent: 20), transactionId: "someTxID", timestamp: Timestamp(from: 1_666_666_000_000), scopes: [scope], + txState: TransactionState(major: .done), txActions: [], - kycUrl: nil) + amountRaw: Amount(currency: LONGCURRENCY, cent: 20), + amountEffective: Amount(currency: LONGCURRENCY, cent: 10)) // let test = Amount(currency: TESTCURRENCY, cent: 123) // let demo = Amount(currency: DEMOCURRENCY, cent: 123456) List { diff --git a/TalerWallet1/Views/Transactions/TransactionRowView.swift b/TalerWallet1/Views/Transactions/TransactionRowView.swift @@ -15,23 +15,39 @@ struct TransactionRowView: View { @Environment(\.sizeCategory) var sizeCategory @Environment(\.colorScheme) private var colorScheme @Environment(\.colorSchemeContrast) private var colorSchemeContrast + @AppStorage("minimalistic") var minimalistic: Bool = false func needVStack(available: CGFloat, contentWidth: CGFloat, valueWidth: CGFloat) -> Bool { available < (contentWidth + valueWidth + 40) } - func topString() -> String { + func topString(forA11y: Bool = false) -> String? { switch transaction { case .payment(let paymentTransaction): return paymentTransaction.details.info.merchant.name default: - return transaction.isDone ? transaction.localizedTypePast - : transaction.localizedType + let result = transaction.isDone ? transaction.localizedTypePast + : transaction.localizedType + return forA11y ? result + : minimalistic ? nil + : result + } + } + + func amount() -> Amount { + switch transaction { + case .refresh(let refreshTransaction): + let details = refreshTransaction.details + return details.refreshInputAmount + default: + let common = transaction.common + let eff = common.amountEffective + if !eff.isZero { return eff } + return common.amountRaw } } var body: some View { - let common = transaction.common let pending = transaction.isPending let needsKYC = transaction.isPendingKYC let shouldConfirm = transaction.shouldConfirm @@ -40,13 +56,13 @@ struct TransactionRowView: View { let increasedContrast = colorSchemeContrast == .increased let details = transaction.detailsToShow() let keys = details.keys - + let common = transaction.common + let isZero = common.amountEffective.isZero let incoming = common.incoming() let textColor = doneOrPending ? .primary : colorScheme == .dark ? .secondary : increasedContrast ? Color(.darkGray) : .secondary // Color(.tertiaryLabel) - let isZero = common.amountEffective.isZero let refreshZero = common.type.isRefresh && isZero let foreColor = refreshZero ? textColor : pending ? WalletColors().pendingColor(incoming) @@ -56,18 +72,20 @@ struct TransactionRowView: View { let iconBadge = TransactionIconBadge(type: common.type, foreColor: foreColor, done: done, incoming: incoming, shouldConfirm: shouldConfirm, needsKYC: needsKYC) - let amountV = AmountV(scope, isZero ? common.amountRaw : common.amountEffective, - isNegative: isZero ? nil : !incoming, strikethrough: !doneOrPending) + let amountV = AmountV(scope, amount(), isNegative: isZero ? nil : !incoming, + strikethrough: !doneOrPending) .foregroundColor(foreColor) + let topA11y = topString(forA11y: true) let topString = topString() - let centerTop = Text(topString) + let centerTop = Text(topString ?? EMPTYSTRING) .foregroundColor(textColor) .strikethrough(!doneOrPending, color: .red) .talerFont(.headline) // .fontWeight(.medium) iOS 16 .padding(.bottom, -2.0) - .accessibilityLabel(doneOrPending ? topString : topString + String(localized: ", canceled", comment: "VoiceOver")) + .accessibilityLabel(doneOrPending ? topA11y! + : topA11y! + String(localized: ", canceled", comment: "VoiceOver")) let centerBottom = TimelineView(.everyMinute) { context in let (dateString, date) = TalerDater.dateString(from: common.timestamp, relative: true) Text(dateString) @@ -85,7 +103,7 @@ struct TransactionRowView: View { let layout1 = HStack(spacing: 4) { VStack(alignment: .leading, spacing: 2) { - centerTop + if topString != nil { centerTop } centerBottom } #if DEBUG @@ -96,7 +114,7 @@ struct TransactionRowView: View { } let layout2 = VStack(alignment: .leading, spacing: 2) { - centerTop + if topString != nil { centerTop } HStack(spacing: 6) { centerBottom #if DEBUG @@ -111,16 +129,24 @@ struct TransactionRowView: View { } let layout3 = VStack(spacing: 0) { - HStack(spacing: 8) { + if topString != nil { + HStack(spacing: 8) { centerTop Spacer(minLength: 2) - amountV + amountV + } + centerBottom + } else { + HStack(spacing: 8) { + centerBottom + Spacer(minLength: 2) + amountV + } } - centerBottom } let layout4 = VStack(alignment: .leading, spacing: 2) { - centerTop + if topString != nil { centerTop } HStack(spacing: -4) { Spacer(minLength: 2) #if DEBUG @@ -207,15 +233,13 @@ extension Transaction { // for PreViews let raw = Amount(currency: LONGCURRENCY, cent: 500) let eff = Amount(currency: LONGCURRENCY, cent: incoming ? 480 : 520) let common = TransactionCommon(type: incoming ? .withdrawal : .payment, - txState: TransactionState(major: pending ? TransactionMajorState.pending - : TransactionMajorState.done), - amountEffective: eff, - amountRaw: raw, transactionId: id, timestamp: time, scopes: [], + txState: txState, txActions: [.abort], - kycUrl: nil) + amountRaw: raw, + amountEffective: eff) if incoming { // if pending then manual else bank-integrated let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company&amount=KUDOS%3A9.99&message=Taler+Withdrawal+J41FQPJGAP1BED1SFSXHC989EN8HRDYAHK688MQ228H6SKBMV0AG" diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift @@ -14,14 +14,13 @@ extension Transaction { // for Dummys let amount = Amount.zero(currency: dummyCurrency) let now = Timestamp.now() let common = TransactionCommon(type: .dummy, - txState: TransactionState(major: .pending), - amountEffective: amount, - amountRaw: amount, transactionId: EMPTYSTRING, timestamp: now, scopes: [], + txState: TransactionState(major: .pending), txActions: [], - kycUrl: nil) + amountRaw: amount, + amountEffective: amount) self = .dummy(DummyTransaction(common: common)) } } @@ -44,6 +43,7 @@ struct TransactionSummaryV: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.colorSchemeContrast) private var colorSchemeContrast @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> + @AppStorage("minimalistic") var minimalistic: Bool = false @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic #if DEBUG @AppStorage("developerMode") var developerMode: Bool = true @@ -118,6 +118,7 @@ struct TransactionSummaryV: View { let pending = transaction.isPending let locale = TalerDater.shared.locale let (dateString, date) = TalerDater.dateString(from: common.timestamp) +// let (dateString, date) = TalerDater.dateString(common.timestamp, minimalistic) let a11yDate = TalerDater.accessibilityDate(date) ?? dateString let navTitle2 = transaction.isDone ? transaction.localizedTypePast : transaction.localizedType @@ -456,6 +457,7 @@ struct TransactionSummaryV: View { if !transaction.isDone { let expiration = details.info.expiration let (dateString, date) = TalerDater.dateString(from: expiration) +// let (dateString, date) = TalerDater.dateString(expiration, minimalistic) let a11yDate = TalerDater.accessibilityDate(date) ?? dateString let a11yLabel = String(localized: "Expires: \(a11yDate)", comment: "VoiceOver") Text("Expires: \(dateString)")