taler-ios

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

commit 467f10e6570b141af7ccda2da18a5120f48842e1
parent aa1996a6cfde4faf88877407aa4092b3d18784bc
Author: Marc Stibane <marc@taler.net>
Date:   Fri, 26 Jun 2026 08:36:52 +0200

improve V1 and V0

Diffstat:
MTalerWallet1/Model/Model+Payment.swift | 6+-----
MTalerWallet1/Resources/Localizable.xcstrings | 1+
MTalerWallet1/Views/Sheets/Payment/ChoicesView.swift | 14+++++++++-----
MTalerWallet1/Views/Sheets/Payment/PaymentView.swift | 2--
MTalerWallet1/Views/Transactions/ThreeAmountsSection.swift | 12++++++------
MTalerWallet1/Views/Transactions/TransactionSummaryList.swift | 61+++++++++++++++++++++++++++++++++++++++++++++++++++----------
6 files changed, 68 insertions(+), 28 deletions(-)

diff --git a/TalerWallet1/Model/Model+Payment.swift b/TalerWallet1/Model/Model+Payment.swift @@ -190,9 +190,7 @@ struct MerchantContractTerms: Codable { let defaultMoneyPot: Int? -// deprecated let wireFeeAmortization: Int? // Share of the wire fee that must be settled with one payment -// let maxWireFee: Amount? // Maximum wire fee that the merchant agrees to pay for -// let auditors: [Auditor]? +// deprecated let auditors: [Auditor]? // ContractTermsV1 let choices: [ContractChoice]? @@ -232,8 +230,6 @@ struct MerchantContractTerms: Codable { case minimumAge = "minimum_age" case defaultMoneyPot = "default_money_pot" -// case wireFeeAmortization = "wire_fee_amortization" -// case maxWireFee = "max_wire_fee" // case auditors case choices case tokenFamilies = "token_families" diff --git a/TalerWallet1/Resources/Localizable.xcstrings b/TalerWallet1/Resources/Localizable.xcstrings @@ -10049,6 +10049,7 @@ }, "No description" : { "comment" : "contractChoice.description", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { diff --git a/TalerWallet1/Views/Sheets/Payment/ChoicesView.swift b/TalerWallet1/Views/Sheets/Payment/ChoicesView.swift @@ -13,6 +13,7 @@ typealias ChoiceTriple = (ChoiceSelectionDetail, ContractChoice, Int) struct ChoicesView: View, Sendable { let stack: CallStack let choiceTriple: [ChoiceTriple] + let showHeader: Bool let automaticIndex: Int? @Binding var selectedChoice: Int @@ -31,7 +32,7 @@ struct ChoicesView: View, Sendable { .foregroundColor(Color.clear) } } - func description(_ contractChoice: ContractChoice) -> String { + func description(_ contractChoice: ContractChoice) -> String? { if let i18nDict = contractChoice.descriptionI18n { if !i18nDict.isEmpty { for code in Locale.preferredLanguageCodes { @@ -44,7 +45,7 @@ struct ChoicesView: View, Sendable { if let desc = contractChoice.description { return desc } - return String(localized: "No description", comment: "contractChoice.description") + return nil } var body: some View { @@ -60,8 +61,7 @@ struct ChoicesView: View, Sendable { /// If it only contains tokens, and insufficient, then hide it. Most probably there is a money payment option to acquire those tokens, which is advertisement enough. /// Tokens only, paymentPossible. If automaticIndex, then also hide it (and auto-pay it). Otherwise show it, so the user can select it. if (!amount.isZero || isPaymentPossible) && !payAutomatic { - let description = description(contractChoice) - Section(header: Text(description)) { + Section { let amountV = HStack { Spacer() AmountV(stack: stack.push(), @@ -84,7 +84,11 @@ struct ChoicesView: View, Sendable { } else { amountV } - } + } header: { + if showHeader { + if let description = description(contractChoice) { + Text(description) + } } } } } } diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift @@ -311,8 +311,6 @@ struct PaymentURIView_Previews: PreviewProvider { publicReorderURL: "publicReorderURL", fulfillmentMessage: nil, fulfillmentMessageI18n: nil, - wireFeeAmortization: 0, - maxWireFee: Amount(currency: LONGCURRENCY, cent: 20), minimumAge: nil // extra: extra, // auditors: [] diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift b/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift @@ -29,14 +29,14 @@ struct ThreeAmountsSheet: View { // should be in a separate file #endif var body: some View { - let raw = common.amountRaw - let effective = common.amountEffective - let fee = common.fee() let incoming = common.isIncoming let pending = common.isPending || common.isFinalizing let dialog = common.isDialog let isDone = common.isDone - let incomplete = !(isDone || pending) + let incomplete = !(isDone || pending || dialog) + let raw = common.amountRaw + let effective: Amount? = incomplete ? nil : common.amountEffective + let fee: Amount? = incomplete ? nil : common.fee() let defaultBottomTitle = incoming ? (pending ? String(localized: "Pending amount to obtain:") : String(localized: "Obtained amount:") ) @@ -59,7 +59,7 @@ struct ThreeAmountsSheet: View { // should be in a separate file feeIsNegative: feeIsNegative, bottomTitle: bottomTitle ?? defaultBottomTitle, bottomAbbrev: bottomAbbrev ?? defaultBottomAbbrev, - bottomAmount: incomplete ? nil : effective, + bottomAmount: effective, large: large, pendingDialog: pending || dialog, isDone: isDone, @@ -196,7 +196,7 @@ struct ThreeAmountsSection: View { color: labelColor, large: false) .padding(.bottom, 4) - if hasNoFees == false { + if hasNoFees == false { // otherwise raw==effective if let fee { let title = minimalistic ? String(localized: "Exchange fee (short):", defaultValue: "Fee:", comment: "short version") : String(localized: "Exchange fee (long):", defaultValue: "Fee:", comment: "long version") diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryList.swift b/TalerWallet1/Views/Transactions/TransactionSummaryList.swift @@ -28,21 +28,47 @@ extension TalerTransaction { // for Dummys struct MerchantHeader: View { let terms: MerchantContractTerms? + func summary(_ terms: MerchantContractTerms) -> String { + if let i18nDict = terms.summaryI18n { + if !i18nDict.isEmpty { + for code in Locale.preferredLanguageCodes { + if let descI18n = i18nDict[code] { + return descI18n + } + } + } + } + return terms.summary + } + var body: some View { if let terms { Section { - Text(terms.summary) + Text(summary(terms)) .talerFont(.title3) } header: { HStack { Spacer() VStack(alignment: .center) { + if let imageBase64 = terms.merchant.logo { + if let url = NSURL(string: imageBase64) { + if let data = NSData(contentsOf: url as URL) { + if let uiImage = UIImage(data: data as Data) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxHeight: 60) + } + } + } + } else { #if TALER_NIGHTLY - let imageName = if #available(iOS 17.0, *) { MERCHANT17 } else { MERCHANT14 } - Image(systemName: imageName) - .resizable() - .frame(width: 44, height: 44) + let imageName = if #available(iOS 17.0, *) { MERCHANT17 } else { MERCHANT14 } + Image(systemName: imageName) + .resizable() + .frame(width: 44, height: 44) #endif + } let merchant = terms.merchant.name Text(merchant) .talerFont(.title3) @@ -70,11 +96,25 @@ struct PaymentTransactionView: View { // @State var isLoadingChoices: Bool? = nil @MainActor - func choiceTriple() -> [ChoiceTriple]? { + func choiceTriple() -> ([ChoiceTriple], Bool)? { if let choicesForPayment { - if let ctChoices = choicesForPayment.contractTerms.choices { - let combined = Array(zip(choicesForPayment.choices, ctChoices, ctChoices.indices)) - return combined + let choices = choicesForPayment.choices + let terms = choicesForPayment.contractTerms + if let ctChoices = terms.choices { + let combined = Array(zip(choices, ctChoices, ctChoices.indices)) + return (combined, true) + } else if let amount = terms.amount { // V0 + let maxFee = terms.maxFee ?? Amount.zero(currency: amount.currencyStr) + let ctChoice = ContractChoice(amount: amount, + maxFee: maxFee, + description: terms.summary, + descriptionI18n: terms.summaryI18n, + inputs: [], + outputs: []) + let combined = Array(zip(choices, [ctChoice], [0])) + return (combined, false) + } else { + symLog.log(" ❗️Yikes, neither choices nor amount in contractTerms!\n\(stack)") } } return nil @@ -121,11 +161,12 @@ struct PaymentTransactionView: View { if common.isDialog { // show payment confirmation dialog MerchantHeader(terms: details.contractTerms) - if let choices = choiceTriple() { + if let (choices, showHeader) = choiceTriple() { let hasAutomatic = choicesForPayment?.automaticExecution ?? false let automaticIndex = hasAutomatic ? choicesForPayment?.automaticExecutableIndex : nil ChoicesView(stack: stack.push(), choiceTriple: choices, + showHeader: showHeader, automaticIndex: automaticIndex, selectedChoice: $selectedChoice) .onChange(of: selectedChoice) { newValue in