From c6044fa73d1c5e46867187d938725b3699d34d3b Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Wed, 26 Nov 2025 14:16:26 +0800 Subject: [PATCH] Show memo in invoice preview --- Mixin.xcodeproj/project.pbxproj | 4 ++ .../AtomicInvoicePaymentOperation.swift | 8 +++- .../Controllers/Wallet/Payment/Invoice.swift | 21 +++++++++- .../Payment/InvoicePaymentOperation.swift | 1 + .../InvoicePreviewViewController.swift | 7 ++++ .../NonAtomicInvoicePaymentOperation.swift | 7 +++- .../Wallet/Payment/TransferExtra.swift | 36 ++++++++++++++++ .../Payment/TransferPaymentOperation.swift | 41 ++----------------- 8 files changed, 82 insertions(+), 43 deletions(-) create mode 100644 Mixin/UserInterface/Controllers/Wallet/Payment/TransferExtra.swift diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 6759fef52a..3ecd3e2cda 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -1413,6 +1413,7 @@ 94E34123261D829200E2F9D3 /* code.css in Resources */ = {isa = PBXBuildFile; fileRef = 94E34120261D829200E2F9D3 /* code.css */; }; 94E34124261D829200E2F9D3 /* highlight.js in Resources */ = {isa = PBXBuildFile; fileRef = 94E34121261D829200E2F9D3 /* highlight.js */; }; 94E35CA3298B836300ADB40D /* WalletConnectService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E35CA2298B836300ADB40D /* WalletConnectService.swift */; }; + 94E41A8B2ED60B1C0019704A /* TransferExtra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E41A8A2ED60B190019704A /* TransferExtra.swift */; }; 94E531BE2D7F5D750008DC76 /* MixinTransactionHistoryTokenFilterPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E531BD2D7F5D750008DC76 /* MixinTransactionHistoryTokenFilterPickerViewController.swift */; }; 94E531CF2D7F62410008DC76 /* Web3TransactionHistoryTokenFilterPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E531CE2D7F62410008DC76 /* Web3TransactionHistoryTokenFilterPickerViewController.swift */; }; 94E533D22D82B52E0008DC76 /* SnapshotTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E533D12D82B5280008DC76 /* SnapshotTableHeaderView.swift */; }; @@ -3192,6 +3193,7 @@ 94E34120261D829200E2F9D3 /* code.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = code.css; sourceTree = ""; }; 94E34121261D829200E2F9D3 /* highlight.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = highlight.js; sourceTree = ""; }; 94E35CA2298B836300ADB40D /* WalletConnectService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectService.swift; sourceTree = ""; }; + 94E41A8A2ED60B190019704A /* TransferExtra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferExtra.swift; sourceTree = ""; }; 94E531BD2D7F5D750008DC76 /* MixinTransactionHistoryTokenFilterPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixinTransactionHistoryTokenFilterPickerViewController.swift; sourceTree = ""; }; 94E531CE2D7F62410008DC76 /* Web3TransactionHistoryTokenFilterPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3TransactionHistoryTokenFilterPickerViewController.swift; sourceTree = ""; }; 94E533D12D82B5280008DC76 /* SnapshotTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotTableHeaderView.swift; sourceTree = ""; }; @@ -5050,6 +5052,7 @@ 94F35A5F2D255B5F001BCAE7 /* Invoice.swift */, 94C5A8922B2157CE00201CDA /* Payment.swift */, 94C6987A2B23257B002377EA /* PaymentPrecondition.swift */, + 94E41A8A2ED60B190019704A /* TransferExtra.swift */, 94A40C462B2781AF0028BC02 /* TransferPaymentOperation.swift */, 945ED2B12B28342A00C138BF /* WithdrawPaymentOperation.swift */, 947084B32D93F88F007DF965 /* InvoicePaymentOperation.swift */, @@ -8348,6 +8351,7 @@ 94735E732E28FED90001C179 /* PrivateKeyViewController.swift in Sources */, 94F2F9812DEF50590013B820 /* CancelPendingMembershipOrderViewController.swift in Sources */, 7B4E307A22966FF900A03F6E /* MediaPreviewMaskView.swift in Sources */, + 94E41A8B2ED60B1C0019704A /* TransferExtra.swift in Sources */, 94FE11F72DA7AC830026AB9A /* Web3TransactionCell.swift in Sources */, 9462A6CB2E2790F3001E4C3E /* ExportImportedSecretValidationViewController.swift in Sources */, 94C0D7E929AD348500E372FC /* InPlaceKeyStorage.swift in Sources */, diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/AtomicInvoicePaymentOperation.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/AtomicInvoicePaymentOperation.swift index 3a3c9c40d0..5039aa258c 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/AtomicInvoicePaymentOperation.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/AtomicInvoicePaymentOperation.swift @@ -104,10 +104,16 @@ final class AtomicInvoicePaymentOperation: InvoicePaymentOperation { let destination: Payment.TransferDestination let transactions: [Transaction] + let memo: TransferExtra? - init(destination: Payment.TransferDestination, transactions: [Transaction]) { + init( + destination: Payment.TransferDestination, + transactions: [Transaction], + memo: TransferExtra?, + ) { self.destination = destination self.transactions = transactions + self.memo = memo } func start(pin: String) async throws { diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/Invoice.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/Invoice.swift index 447b1a1f9d..2a21452c90 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/Invoice.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/Invoice.swift @@ -424,6 +424,18 @@ extension Invoice: PaymentPreconditionChecker { onFailure(reason) } case .passed(let issues): + let memo: TransferExtra? = { + guard let entry = entries.first else { + return nil + } + return if entry.isStorage { + .hexEncoded(entry.extra.hexEncodedString()) + } else if let extra = String(data: entry.extra, encoding: .utf8) { + .plain(extra) + } else { + nil + } + }() if sendingSameTokenMultipleTimes { var transactions: [NonAtomicInvoicePaymentOperation.Transaction] = [] for entry in entries { @@ -439,7 +451,8 @@ extension Invoice: PaymentPreconditionChecker { let operation = NonAtomicInvoicePaymentOperation( destination: destination, transactions: transactions, - paidEntriesHash: paidEntriesHash + paidEntriesHash: paidEntriesHash, + memo: memo, ) await MainActor.run { onSuccess(operation, issues) @@ -470,7 +483,11 @@ extension Invoice: PaymentPreconditionChecker { return } } - let operation = AtomicInvoicePaymentOperation(destination: destination, transactions: transactions) + let operation = AtomicInvoicePaymentOperation( + destination: destination, + transactions: transactions, + memo: memo, + ) await MainActor.run { onSuccess(operation, issues) } diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePaymentOperation.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePaymentOperation.swift index 549c671f85..644c808a40 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePaymentOperation.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePaymentOperation.swift @@ -14,6 +14,7 @@ protocol InvoicePaymentOperation { var destination: Payment.TransferDestination { get } var transactions: [Transaction] { get } + var memo: TransferExtra? { get } func start(pin: String) async throws diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePreviewViewController.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePreviewViewController.swift index 64ce38a013..1388c90132 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePreviewViewController.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePreviewViewController.swift @@ -125,6 +125,13 @@ final class InvoicePreviewViewController: AuthenticationPreviewViewController { )) } + if let memo = operation.memo, + let value = memo.plainValue ?? memo.hexEncodedValue, + !value.isEmpty + { + rows.append(.info(caption: .memo, content: value)) + } + reloadData(with: rows) reporter.report(event: .sendPreview) } diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/NonAtomicInvoicePaymentOperation.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/NonAtomicInvoicePaymentOperation.swift index a3ddeeb8ad..6274db627b 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/NonAtomicInvoicePaymentOperation.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/NonAtomicInvoicePaymentOperation.swift @@ -29,20 +29,23 @@ final class NonAtomicInvoicePaymentOperation: InvoicePaymentOperation { let destination: Payment.TransferDestination let transactions: [Transaction] let paidEntriesHash: [String] + let memo: TransferExtra? init( destination: Payment.TransferDestination, transactions: [Transaction], - paidEntriesHash: [String] + paidEntriesHash: [String], + memo: TransferExtra?, ) { self.destination = destination self.transactions = transactions self.paidEntriesHash = paidEntriesHash + self.memo = memo } func start(pin: String) async throws { // In case of decoding failure, do it earlier to avoid partial success - let extras: [TransferPaymentOperation.Extra] = try transactions.map { transaction in + let extras: [TransferExtra] = try transactions.map { transaction in let entry = transaction.entry if entry.isStorage { return .hexEncoded(entry.extra.hexEncodedString()) diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/TransferExtra.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/TransferExtra.swift new file mode 100644 index 0000000000..7ea5583532 --- /dev/null +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/TransferExtra.swift @@ -0,0 +1,36 @@ +import Foundation + +enum TransferExtra { + + case plain(String) + case hexEncoded(String) + + var plainValue: String? { + switch self { + case .plain(let value): + value + case .hexEncoded(let hexEncoded): + if let data = Data(hexEncodedString: hexEncoded), + let value = String(data: data, encoding: .utf8) + { + value + } else { + nil + } + } + } + + var hexEncodedValue: String? { + switch self { + case .plain(let plain): + if let data = plain.data(using: .utf8) { + data.hexEncodedString() + } else { + nil + } + case .hexEncoded(let value): + value + } + } + +} diff --git a/Mixin/UserInterface/Controllers/Wallet/Payment/TransferPaymentOperation.swift b/Mixin/UserInterface/Controllers/Wallet/Payment/TransferPaymentOperation.swift index dbd12ec1d5..6de77bf1cc 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Payment/TransferPaymentOperation.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Payment/TransferPaymentOperation.swift @@ -48,48 +48,13 @@ final class TransferPaymentOperation { } - enum Extra { - - case plain(String) - case hexEncoded(String) - - var plainValue: String? { - switch self { - case .plain(let value): - value - case .hexEncoded(let hexEncoded): - if let data = Data(hexEncodedString: hexEncoded), - let value = String(data: data, encoding: .utf8) - { - value - } else { - nil - } - } - } - - var hexEncodedValue: String? { - switch self { - case .plain(let plain): - if let data = plain.data(using: .utf8) { - data.hexEncodedString() - } else { - nil - } - case .hexEncoded(let value): - value - } - } - - } - let behavior: Behavior let traceID: String let spendingOutputs: UTXOService.OutputCollection let destination: Payment.TransferDestination let token: MixinTokenItem let amount: Decimal - let extra: Extra + let extra: TransferExtra let reference: String? private(set) var kernelTransactionHash: String? @@ -97,7 +62,7 @@ final class TransferPaymentOperation { private init( behavior: Behavior, traceID: String, spendingOutputs: UTXOService.OutputCollection, destination: Payment.TransferDestination, token: MixinTokenItem, amount: Decimal, - extra: Extra, reference: String? + extra: TransferExtra, reference: String? ) { self.behavior = behavior self.traceID = traceID @@ -112,7 +77,7 @@ final class TransferPaymentOperation { static func transfer( traceID: String, spendingOutputs: UTXOService.OutputCollection, destination: Payment.TransferDestination, token: MixinTokenItem, - amount: Decimal, extra: Extra, reference: String? + amount: Decimal, extra: TransferExtra, reference: String? ) -> TransferPaymentOperation { TransferPaymentOperation( behavior: .transfer,