taler-ios

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

commit 0d10ba1bf758be08efbe7f1c861108df337e0a9d
parent eaf3a0595c549c9152ca323c3b4066339c39086b
Author: Marc Stibane <marc@taler.net>
Date:   Thu,  4 Jan 2024 17:46:24 +0100

use minor for Status

Diffstat:
MTalerWallet1/Model/Transaction.swift | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
MTalerWallet1/Views/Transactions/TransactionDetailView.swift | 87++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
2 files changed, 108 insertions(+), 41 deletions(-)

diff --git a/TalerWallet1/Model/Transaction.swift b/TalerWallet1/Model/Transaction.swift @@ -16,7 +16,7 @@ enum TransactionDecodingError: Error { } enum TransactionMinorState: String, Codable { - // Placeholder until D37 is fully implemented + // Placeholder until D37 is fully implemented case unknown case deposit case kyc // KycRequired @@ -26,6 +26,7 @@ enum TransactionMinorState: String, Codable { case submitPayment = "submit-payment" case rebindSession = "rebind-session" case refresh + case refreshExpired = "refresh-expired" case pickup case autoRefund = "auto-refund" case user @@ -50,6 +51,46 @@ enum TransactionMinorState: String, Codable { case proposed case refundAvailable = "refund-available" case acceptRefund = "accept-refund" + + var localizedState: String? { + switch self { + case .unknown: return self.rawValue + case .deposit: return self.rawValue + case .kyc: return String(localized: "MinorState.kyc", defaultValue: "KYC required", comment: "TxMinorState heading") + case .aml: return String(localized: "MinorState.aml", defaultValue: "AML required", comment: "TxMinorState heading") + case .mergeKycRequired: return String(localized: "MinorState.mergekyc", defaultValue: "KYC merge", comment: "TxMinorState heading") + case .track: return self.rawValue + case .submitPayment: return self.rawValue + case .rebindSession: return self.rawValue + case .refresh: return self.rawValue + case .refreshExpired: return self.rawValue + case .pickup: return self.rawValue + case .autoRefund: return self.rawValue + case .user: return self.rawValue + case .bank: return self.rawValue + case .exchange: return self.rawValue + case .claimProposal: return self.rawValue + case .checkRefund: return self.rawValue + case .createPurse: return self.rawValue + case .deletePurse: return self.rawValue + case .ready: return self.rawValue + case .merge: return self.rawValue + case .repurchase: return self.rawValue + case .bankRegisterReserve: return self.rawValue + case .bankConfirmTransfer: return String(localized: "MinorState.bankConfirmTransfer", defaultValue: "Waiting for bank", comment: "TxMinorState heading") + case .withdrawCoins: return self.rawValue + case .exchangeWaitReserve: return self.rawValue + case .abortingBank: return self.rawValue + case .aborting: return self.rawValue + case .refused: return self.rawValue + case .withdraw: return self.rawValue + case .merchantOrderProposed: return self.rawValue + case .proposed: return self.rawValue + case .refundAvailable: return self.rawValue + case .acceptRefund: return self.rawValue +// default: return nil + } + } } enum TransactionMajorState: String, Codable { @@ -264,13 +305,20 @@ struct WithdrawalTransaction : Sendable{ var details: WithdrawalTransactionDetails } +struct RefundInfo: Decodable { + var amountEffective: Amount + var amountRaw: Amount + var transactionId: String + var timestamp: Timestamp +} + struct PaymentTransactionDetails: Decodable { var info: OrderShortInfo var proposalId: String var totalRefundRaw: Amount var totalRefundEffective: Amount var refundPending: Amount? - var refunds: [String]? // array of refund txIDs for this payment + var refunds: [RefundInfo]? // array of refund txIDs for this payment var refundQueryActive: Bool? var posConfirmation: String? } @@ -312,15 +360,24 @@ enum RefreshReason: String, Decodable { case refund case abortPay = "abort-pay" case abortDeposit = "abort-deposit" + case abortPeerPushDebit = "abort-peer-push-debit" case recoup case backupRestored = "backup-restored" case scheduled } +struct RefreshError: Decodable { + var code: Int + var when: Timestamp + var hint: String + var numErrors: Int + var errors: [HTTPError] +} struct RefreshTransactionDetails: Decodable { var refreshReason: RefreshReason var originatingTransactionId: String? var refreshInputAmount: Amount var refreshOutputAmount: Amount + var error: RefreshError? } struct RefreshTransaction : Sendable{ @@ -451,6 +508,7 @@ enum Transaction: Decodable, Hashable, Identifiable, Sendable { var isAbortable : Bool { common.txActions.contains(.abort) } var isFailable : Bool { common.txActions.contains(.fail) } var isDeleteable : Bool { common.txActions.contains(.delete) } + var isRetryable : Bool { common.txActions.contains(.retry) } var isResumable : Bool { common.txActions.contains(.resume) } var isSuspendable: Bool { common.txActions.contains(.suspend) } diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift b/TalerWallet1/Views/Transactions/TransactionDetailView.swift @@ -114,11 +114,11 @@ struct TransactionDetailView: View { if developerMode { if transaction.isSuspendable { if let suspendAction { TransactionButton(transactionId: common.transactionId, - command: .suspend, action: suspendAction) + command: .suspend, warning: nil, action: suspendAction) } } if transaction.isResumable { if let resumeAction { TransactionButton(transactionId: common.transactionId, - command: .resume, action: resumeAction) + command: .resume, warning: nil, action: resumeAction) } } } // Suspend + Resume buttons Text(dateString) @@ -126,27 +126,38 @@ struct TransactionDetailView: View { .accessibilityLabel(accessibilityDate) .foregroundColor(colorSchemeContrast == .increased ? .primary : .secondary) .listRowSeparator(.hidden) - HStack { - Text(verbatim: "|") // only reason for this leading-aligned text is to get a nice full length listRowSeparator - .accessibilityHidden(true) - .foregroundColor(Color.clear) - Spacer() - Text("Status: \(common.txState.major.localizedState)") + VStack(alignment: .trailing) { + let minorState = common.txState.minor?.localizedState ?? nil + HStack { + Text(verbatim: "|") // only reason for this leading-aligned text is to get a nice full length listRowSeparator + .accessibilityHidden(true) + .foregroundColor(Color.clear) + Spacer() + Text("Status: \(minorState ?? common.txState.major.localizedState)") + .multilineTextAlignment(.trailing) + } } .listRowSeparator(.automatic) .accessibilityFont(.title) TypeDetail(transaction: $transaction, hasDone: doneAction != nil) +// if transaction.isRetryable { if let retryAction { +// TransactionButton(transactionId: common.transactionId, command: .retry, +// warning: nil, action: abortAction) +// } } // Retry button if transaction.isAbortable { if let abortAction { - TransactionButton(transactionId: common.transactionId, - command: .abort, action: abortAction) + TransactionButton(transactionId: common.transactionId, command: .abort, + warning: String(localized: "Are you sure you want to abort this transaction?"), + action: abortAction) } } // Abort button if transaction.isFailable { if let failAction { - TransactionButton(transactionId: common.transactionId, - command: .fail, action: failAction) + TransactionButton(transactionId: common.transactionId, command: .fail, + warning: String(localized: "Are you sure you want to fail this transaction?"), + action: failAction) } } // Fail button if transaction.isDeleteable { if let deleteAction { - TransactionButton(transactionId: common.transactionId, - command: .delete, action: deleteAction) + TransactionButton(transactionId: common.transactionId, command: .delete, + warning: String(localized: "Are you sure you want to delete this transaction?"), + action: deleteAction) } } // Delete button if let doneAction { Button(transaction.shouldConfirm ? "Confirm later" : "Done", action: doneAction) @@ -154,8 +165,6 @@ struct TransactionDetailView: View { } // Done button }.id(viewId) // change viewId to enforce a draw update .listStyle(myListStyle.style).anyView -// .safeAreaInset(edge: .bottom) { -// } } // Group .onNotification(.TransactionExpired) { notification in // TODO: Alert user that this tx just expired @@ -217,12 +226,12 @@ struct TransactionDetailView: View { var body: some View { VStack(alignment: .leading) { // Show Hint that User should Confirm on bank website if !iconOnly { - Text("Waiting for bank confirmation") - .fixedSize(horizontal: false, vertical: true) // wrap in scrollview + Text("The bank is waiting for your confirmation.") +// .fixedSize(horizontal: false, vertical: true) // wrap in scrollview .multilineTextAlignment(.leading) // otherwise .listRowSeparator(.hidden) } - Link("Confirm with bank", destination: destination) + Link("Confirm now", destination: destination) .buttonStyle(TalerButtonStyle(type: .prominent, badge: CONFIRM_BANK)) .accessibilityHint("Will go to bank website to confirm this withdrawal.") } @@ -239,10 +248,10 @@ struct TransactionDetailView: View { Group { switch transaction { case .dummy(_): - let title = "" + let title = EMPTYSTRING Text(title) .accessibilityFont(.body) - case .withdrawal(let withdrawalTransaction): + case .withdrawal(let withdrawalTransaction): Group { let details = withdrawalTransaction.details if pending { if transaction.isPendingKYC { @@ -257,50 +266,49 @@ struct TransactionDetailView: View { case .manual: // "Make a wire transfer of \(amount) to" ManualDetailsV(common: common, details: withdrawalDetails) - case .bankIntegrated: // "Confirm with bank" - if !transaction.isPendingKYC { // both should never happen, but... + case .bankIntegrated: // "Confirm now" (with bank) + if !transaction.isPendingKYC { // cannot confirm if KYC is needed first let confirmed = withdrawalDetails.confirmed ?? false if !confirmed { if let confirmationUrl = withdrawalDetails.bankConfirmationUrl { if let destination = URL(string: confirmationUrl) { ConfirmationButton(destination: destination) - } - } - } - } + } } } } } // switch - } // ManualDetails or Confirm with bank + } // ManualDetails or Confirm now (with bank) ThreeAmountsSheet(common: common, topAbbrev: String(localized: "Chosen:"), topTitle: String(localized: "Chosen amount to withdraw:"), baseURL: details.exchangeBaseUrl, large: false, summary: nil) - case .payment(let paymentTransaction): + } + case .payment(let paymentTransaction): Group { let details = paymentTransaction.details Text(details.info.summary) .accessibilityFont(.title3) ThreeAmountsSheet(common: common, topAbbrev: String(localized: "Pay:"), topTitle: String(localized: "Sum to be paid:"), baseURL: nil, large: true, summary: details.info.summary) // TODO: baseURL - case .refund(let refundTransaction): + } + case .refund(let refundTransaction): Group { let details = refundTransaction.details // TODO: more details ThreeAmountsSheet(common: common, topAbbrev: String(localized: "Refunded:"), topTitle: String(localized: "Refunded amount:"), baseURL: nil, large: true, summary: nil) // TODO: baseURL, summary - case .reward(let rewardTransaction): - let details = rewardTransaction.details // TODO: more details + } + case .reward(let rewardTransaction): Group { + let details = rewardTransaction.details ThreeAmountsSheet(common: common, topAbbrev: String(localized: "Reward:"), topTitle: String(localized: "Received Reward:"), baseURL: details.exchangeBaseUrl, large: true, summary: nil) // TODO: summary - case .refresh(let refreshTransaction): + } + case .refresh(let refreshTransaction): Group { let details = refreshTransaction.details // TODO: details + Text(details.refreshReason.rawValue) ThreeAmountsSheet(common: common, topAbbrev: String(localized: "Refreshed:"), topTitle: String(localized: "Refreshed amount:"), baseURL: nil, large: true, summary: nil) // TODO: baseURL - case .peer2peer(let p2pTransaction): - let details = p2pTransaction.details // TODO: details - Text(details.info.summary) - .accessibilityFont(.title2) - .lineLimit(4) - .padding(.bottom) + } + case .peer2peer(let p2pTransaction): Group { + let details = p2pTransaction.details // 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 { @@ -322,6 +330,7 @@ struct TransactionDetailView: View { topTitle: transaction.localizedType + colon, baseURL: details.exchangeBaseUrl, large: false, summary: details.info.summary) + } // p2p } // switch } // Group }