From 6a2b59df60fce73816b67f66b5b975791b2054ac Mon Sep 17 00:00:00 2001 From: wuyueyang Date: Sun, 5 Jan 2025 13:20:26 +0800 Subject: [PATCH] Pay invoice --- Mixin.xcodeproj/project.pbxproj | 24 +- Mixin/Resources/en.lproj/Localizable.strings | 3 +- Mixin/Resources/es.lproj/Localizable.strings | 3 +- Mixin/Resources/ja.lproj/Localizable.strings | 3 +- Mixin/Resources/ru.lproj/Localizable.strings | 3 +- .../zh-Hans.lproj/Localizable.strings | 3 +- .../zh-Hant.lproj/Localizable.strings | 3 +- .../AuthenticationPreviewHeaderView.swift | 139 +++++-- .../AuthenticationPreviewViewController.swift | 12 +- ...ll.swift => MultipleAssetChangeCell.swift} | 66 ++- ...geCell.xib => MultipleAssetChangeCell.xib} | 8 +- .../Controllers/Wallet/Payment/Invoice.swift | 75 +++- .../Payment/InvoicePaymentOperation.swift | 383 ++++++++++++++++++ .../InvoicePreviewViewController.swift | 187 +++++++++ .../Controllers/Wallet/Payment/Payment.swift | 110 +---- .../Wallet/Payment/PaymentPrecondition.swift | 77 +++- .../Wallet/Payment/SafePaymentURL.swift | 23 ++ .../Payment/TransferPaymentOperation.swift | 13 +- .../Payment/WithdrawPaymentOperation.swift | 10 +- Mixin/UserInterface/Windows/UrlWindow.swift | 35 ++ .../Database/User/DAO/SafeSnapshotDAO.swift | 13 + .../Tip.framework/Headers/Kernel.objc.h | 4 +- .../Tip.framework/Info.plist | 4 +- .../ios-arm64-simulator/Tip.framework/Tip | Bin 6715496 -> 6716928 bytes .../Tip.framework/Headers/Kernel.objc.h | 4 +- .../ios-arm64/Tip.framework/Info.plist | 4 +- .../ios-arm64/Tip.framework/Tip | Bin 6709824 -> 6711256 bytes 27 files changed, 1013 insertions(+), 196 deletions(-) rename Mixin/UserInterface/Controllers/Wallet/Authentication Preview/{SwapAssetChangeCell.swift => MultipleAssetChangeCell.swift} (61%) rename Mixin/UserInterface/Controllers/Wallet/Authentication Preview/{SwapAssetChangeCell.xib => MultipleAssetChangeCell.xib} (89%) create mode 100644 Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePaymentOperation.swift create mode 100644 Mixin/UserInterface/Controllers/Wallet/Payment/InvoicePreviewViewController.swift diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 4172e31134..ea7d51f363 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -1070,8 +1070,8 @@ 94B13FC32B5ED5F9001333BE /* Web3BrowserView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B13FC12B5ED5F9001333BE /* Web3BrowserView.xib */; }; 94B1E5582C4920C800A2349F /* Web3SwapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B1E5572C4920C800A2349F /* Web3SwapViewController.swift */; }; 94B1E55D2C49222100A2349F /* MixinSwapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B1E55C2C49222100A2349F /* MixinSwapViewController.swift */; }; - 94B1E56B2C4A05BF00A2349F /* SwapAssetChangeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B1E56A2C4A05BF00A2349F /* SwapAssetChangeCell.xib */; }; - 94B1E56C2C4A05BF00A2349F /* SwapAssetChangeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B1E5692C4A05BF00A2349F /* SwapAssetChangeCell.swift */; }; + 94B1E56B2C4A05BF00A2349F /* MultipleAssetChangeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B1E56A2C4A05BF00A2349F /* MultipleAssetChangeCell.xib */; }; + 94B1E56C2C4A05BF00A2349F /* MultipleAssetChangeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B1E5692C4A05BF00A2349F /* MultipleAssetChangeCell.swift */; }; 94B2CD782B0F88D300D268E1 /* DepositView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B2CD772B0F88D300D268E1 /* DepositView.xib */; }; 94B5776A2B5E554800AE576E /* ExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B577692B5E554800AE576E /* ExploreViewController.swift */; }; 94B5776F2B5E555B00AE576E /* ExploreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B5776E2B5E555B00AE576E /* ExploreView.xib */; }; @@ -1221,6 +1221,8 @@ 94F2375C2C691D5C0057D1AB /* TokenPriceChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F2375A2C691D5C0057D1AB /* TokenPriceChartCell.swift */; }; 94F2375D2C691D5C0057D1AB /* TokenPriceChartCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94F2375B2C691D5C0057D1AB /* TokenPriceChartCell.xib */; }; 94F35A602D255B61001BCAE7 /* Invoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F35A5F2D255B5F001BCAE7 /* Invoice.swift */; }; + 94F35B372D27C625001BCAE7 /* InvoicePaymentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F35B362D27C620001BCAE7 /* InvoicePaymentOperation.swift */; }; + 94F35B872D2818B2001BCAE7 /* InvoicePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F35B862D2818B2001BCAE7 /* InvoicePreviewViewController.swift */; }; 94F36AC626CA59B300AC30A5 /* PhoneNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F36AC526CA59B300AC30A5 /* PhoneNumberValidator.swift */; }; 94F952582AE616F40025B995 /* TransferOutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F952572AE616F40025B995 /* TransferOutViewController.swift */; }; 94F9525D2AE6174A0025B995 /* TransferOutView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94F9525C2AE6174A0025B995 /* TransferOutView.xib */; }; @@ -2567,8 +2569,8 @@ 94B13FC12B5ED5F9001333BE /* Web3BrowserView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Web3BrowserView.xib; sourceTree = ""; }; 94B1E5572C4920C800A2349F /* Web3SwapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3SwapViewController.swift; sourceTree = ""; }; 94B1E55C2C49222100A2349F /* MixinSwapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixinSwapViewController.swift; sourceTree = ""; }; - 94B1E5692C4A05BF00A2349F /* SwapAssetChangeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwapAssetChangeCell.swift; sourceTree = ""; }; - 94B1E56A2C4A05BF00A2349F /* SwapAssetChangeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SwapAssetChangeCell.xib; sourceTree = ""; }; + 94B1E5692C4A05BF00A2349F /* MultipleAssetChangeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAssetChangeCell.swift; sourceTree = ""; }; + 94B1E56A2C4A05BF00A2349F /* MultipleAssetChangeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MultipleAssetChangeCell.xib; sourceTree = ""; }; 94B2CD772B0F88D300D268E1 /* DepositView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DepositView.xib; sourceTree = ""; }; 94B577692B5E554800AE576E /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = ""; }; 94B5776E2B5E555B00AE576E /* ExploreView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExploreView.xib; sourceTree = ""; }; @@ -2717,6 +2719,8 @@ 94F2375A2C691D5C0057D1AB /* TokenPriceChartCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenPriceChartCell.swift; sourceTree = ""; }; 94F2375B2C691D5C0057D1AB /* TokenPriceChartCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenPriceChartCell.xib; sourceTree = ""; }; 94F35A5F2D255B5F001BCAE7 /* Invoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Invoice.swift; sourceTree = ""; }; + 94F35B362D27C620001BCAE7 /* InvoicePaymentOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicePaymentOperation.swift; sourceTree = ""; }; + 94F35B862D2818B2001BCAE7 /* InvoicePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicePreviewViewController.swift; sourceTree = ""; }; 94F36AC526CA59B300AC30A5 /* PhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidator.swift; sourceTree = ""; }; 94F952572AE616F40025B995 /* TransferOutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferOutViewController.swift; sourceTree = ""; }; 94F9525C2AE6174A0025B995 /* TransferOutView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransferOutView.xib; sourceTree = ""; }; @@ -4086,8 +4090,8 @@ 941979532BA47782002BA19F /* Web3MessageCell.swift */, 94D308762BA72C8F0017494C /* Web3AmountChangeCell.xib */, 94D308752BA72C8F0017494C /* Web3AmountChangeCell.swift */, - 94B1E56A2C4A05BF00A2349F /* SwapAssetChangeCell.xib */, - 94B1E5692C4A05BF00A2349F /* SwapAssetChangeCell.swift */, + 94B1E56A2C4A05BF00A2349F /* MultipleAssetChangeCell.xib */, + 94B1E5692C4A05BF00A2349F /* MultipleAssetChangeCell.swift */, 94A0C8C12C77259E00BDE672 /* AddressReceiversCell.xib */, 94A0C8C02C77259E00BDE672 /* AddressReceiversCell.swift */, 9457F7712B6F4186008FC98F /* AuthenticationPreviewSingleButtonTrayView.swift */, @@ -4237,8 +4241,10 @@ 94C6987A2B23257B002377EA /* PaymentPrecondition.swift */, 94A40C462B2781AF0028BC02 /* TransferPaymentOperation.swift */, 945ED2B12B28342A00C138BF /* WithdrawPaymentOperation.swift */, + 94F35B362D27C620001BCAE7 /* InvoicePaymentOperation.swift */, 944C61FE2B6C033500C7DF06 /* EditAddressPreviewViewController.swift */, 9457F7622B6EEAFA008FC98F /* TransferPreviewViewController.swift */, + 94F35B862D2818B2001BCAE7 /* InvoicePreviewViewController.swift */, 94B6EDFD2B707F15001DCC50 /* WithdrawPreviewViewController.swift */, 94EE1DC62B721E4800C32DD4 /* MultisigPreviewViewController.swift */, 94F952612AE623B50025B995 /* TokenSelectorViewController.swift */, @@ -5570,7 +5576,7 @@ 7B4C656F242354AE003B78F9 /* LocationSearchNoResultView.xib in Resources */, 7B3FD4EE241965BE00B58006 /* LocationView.xib in Resources */, 942FB8932C8AD7F800C8025C /* ShareInscriptionAsPictureView.xib in Resources */, - 94B1E56B2C4A05BF00A2349F /* SwapAssetChangeCell.xib in Resources */, + 94B1E56B2C4A05BF00A2349F /* MultipleAssetChangeCell.xib in Resources */, 7C11E34A281BF4BC00D3362B /* ic_time_animation_dark@2x.gif in Resources */, 945138B82C6622F600839CBB /* TokenMarketCell.xib in Resources */, 942DB9A92CAAA14900FFCB40 /* AllAlertsTopActionView.xib in Resources */, @@ -6379,7 +6385,7 @@ 7B54F95422B23A5600908A9D /* RecoveryContactSelectorViewController.swift in Sources */, 94EE1C012B4C647500BE9AD1 /* CodeURL.swift in Sources */, DF3FF0552011E9B8000A0C0A /* FileUploadJob.swift in Sources */, - 94B1E56C2C4A05BF00A2349F /* SwapAssetChangeCell.swift in Sources */, + 94B1E56C2C4A05BF00A2349F /* MultipleAssetChangeCell.swift in Sources */, 7CC2226C29D917D00027FAAB /* DeviceTransferRecordType.swift in Sources */, 7BBCEC382523A2B400F270DF /* MinimizedClipSwitcherViewController.swift in Sources */, E0C7674D23CC9411003F9215 /* BackgroundedTrailingInfoViewModel.swift in Sources */, @@ -6622,6 +6628,7 @@ 7CEB735E29DBC44A006FB5B2 /* DeviceTransferParticipant.swift in Sources */, 94E9C0962BC6978F00D6157C /* Web3PopupCoordinator.swift in Sources */, 7CEB735C29DBB737006FB5B2 /* DeviceTransferConversation.swift in Sources */, + 94F35B372D27C625001BCAE7 /* InvoicePaymentOperation.swift in Sources */, 7B0B01921FEA19BA000EEE4F /* DecryptionFailedMessageViewModel.swift in Sources */, 7B2E3E4B1FA07F4500DDDDEB /* SelectCountryViewController.swift in Sources */, 7BA931F51FC08DF4005DF478 /* TextMessageCell.swift in Sources */, @@ -7104,6 +7111,7 @@ 7B9A090525481ADB00E8568D /* MinimizedClipIconView.swift in Sources */, 943A28082B878FCE0050929A /* TIPNodeResponseError+Localization.swift in Sources */, 7B23FEA52229538B00F912EC /* PhotoInputAlbumCell.swift in Sources */, + 94F35B872D2818B2001BCAE7 /* InvoicePreviewViewController.swift in Sources */, 94B7B6DE26B43581000B0AC5 /* SilentNotificationMessagePreviewViewController.swift in Sources */, 7B37B16323ED483D00590215 /* GalleryTransitionFromNonQuotingMessageCellView.swift in Sources */, 7B40207E215B7046008FF42E /* AttachmentExpirationHintingMessageCell.swift in Sources */, diff --git a/Mixin/Resources/en.lproj/Localizable.strings b/Mixin/Resources/en.lproj/Localizable.strings index 6c4ca6cb7d..610d92f530 100644 --- a/Mixin/Resources/en.lproj/Localizable.strings +++ b/Mixin/Resources/en.lproj/Localizable.strings @@ -146,6 +146,8 @@ "approving_transaction" = "Approving Transaction"; "approving_transaction_failed" = "Approving Transaction Failed"; "ask_me_anything" = "Ask me anything"; +"asset_changes" = "Asset changes"; +"asset_changes_estimate" = "Asset changes (estimate)"; "asset_key" = "Asset Key"; "asset_not_found" = "Asset not found."; "asset_type" = "Asset Type"; @@ -1197,7 +1199,6 @@ "suspended_deposit" = "The deposit for %1$@ has been temporarily suspended. This action has been taken due to potential factors such as low utilization, node failure, blockchain network hard fork, or congestion. We kindly request your attention to the fact that the %2$@ assets already stored in the wallet remain transferable."; "suspicious_link" = "Suspicious Link"; "swap" = "Swap"; -"swap_asset_change" = "Asset changes (estimate)"; "swap_confirmation" = "Swap Confirmation"; "swap_failed" = "Swap Failed"; "swap_invalid_amount" = "No available quote found. Please try a different amount."; diff --git a/Mixin/Resources/es.lproj/Localizable.strings b/Mixin/Resources/es.lproj/Localizable.strings index fbeaa3f51d..cb225ca872 100644 --- a/Mixin/Resources/es.lproj/Localizable.strings +++ b/Mixin/Resources/es.lproj/Localizable.strings @@ -146,6 +146,8 @@ "approving_transaction" = "Approving Transaction"; "approving_transaction_failed" = "Approving Transaction Failed"; "ask_me_anything" = "Ask me anything"; +"asset_changes" = "Asset changes"; +"asset_changes_estimate" = "Asset changes (estimate)"; "asset_key" = "Clave de activo"; "asset_not_found" = "Activo no encontrado."; "asset_type" = "Tipo de activo"; @@ -1197,7 +1199,6 @@ "suspended_deposit" = "The deposit for %1$@ has been temporarily suspended. This action has been taken due to potential factors such as low utilization, node failure, blockchain network hard fork, or congestion. We kindly request your attention to the fact that the %2$@ assets already stored in the wallet remain transferable."; "suspicious_link" = "Enlace sospechoso"; "swap" = "Swap"; -"swap_asset_change" = "Asset changes (estimate)"; "swap_confirmation" = "Swap Confirmation"; "swap_failed" = "Swap Failed"; "swap_invalid_amount" = "No available quote found. Please try a different amount."; diff --git a/Mixin/Resources/ja.lproj/Localizable.strings b/Mixin/Resources/ja.lproj/Localizable.strings index 7c7557e0af..04450c14ad 100644 --- a/Mixin/Resources/ja.lproj/Localizable.strings +++ b/Mixin/Resources/ja.lproj/Localizable.strings @@ -146,6 +146,8 @@ "approving_transaction" = "Approving Transaction"; "approving_transaction_failed" = "Approving Transaction Failed"; "ask_me_anything" = "Ask me anything"; +"asset_changes" = "Asset changes"; +"asset_changes_estimate" = "Asset changes (estimate)"; "asset_key" = "資産のキー"; "asset_not_found" = "資産が存在しません"; "asset_type" = "資産タイプ"; @@ -1197,7 +1199,6 @@ "suspended_deposit" = "The deposit for %1$@ has been temporarily suspended. This action has been taken due to potential factors such as low utilization, node failure, blockchain network hard fork, or congestion. We kindly request your attention to the fact that the %2$@ assets already stored in the wallet remain transferable."; "suspicious_link" = "不審なリンクです"; "swap" = "Swap"; -"swap_asset_change" = "Asset changes (estimate)"; "swap_confirmation" = "Swap Confirmation"; "swap_failed" = "Swap Failed"; "swap_invalid_amount" = "No available quote found. Please try a different amount."; diff --git a/Mixin/Resources/ru.lproj/Localizable.strings b/Mixin/Resources/ru.lproj/Localizable.strings index 3a6594e9a0..ed8601191c 100644 --- a/Mixin/Resources/ru.lproj/Localizable.strings +++ b/Mixin/Resources/ru.lproj/Localizable.strings @@ -146,6 +146,8 @@ "approving_transaction" = "Approving Transaction"; "approving_transaction_failed" = "Approving Transaction Failed"; "ask_me_anything" = "Ask me anything"; +"asset_changes" = "Asset changes"; +"asset_changes_estimate" = "Asset changes (estimate)"; "asset_key" = "Ключ актива"; "asset_not_found" = "Актив не найден."; "asset_type" = "Тип актива"; @@ -1197,7 +1199,6 @@ "suspended_deposit" = "The deposit for %1$@ has been temporarily suspended. This action has been taken due to potential factors such as low utilization, node failure, blockchain network hard fork, or congestion. We kindly request your attention to the fact that the %2$@ assets already stored in the wallet remain transferable."; "suspicious_link" = "Подозрительная ссылка"; "swap" = "Swap"; -"swap_asset_change" = "Asset changes (estimate)"; "swap_confirmation" = "Swap Confirmation"; "swap_failed" = "Swap Failed"; "swap_invalid_amount" = "No available quote found. Please try a different amount."; diff --git a/Mixin/Resources/zh-Hans.lproj/Localizable.strings b/Mixin/Resources/zh-Hans.lproj/Localizable.strings index 91fdb7f9d7..724f49c2ca 100644 --- a/Mixin/Resources/zh-Hans.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hans.lproj/Localizable.strings @@ -146,6 +146,8 @@ "approving_transaction" = "正在批准"; "approving_transaction_failed" = "批准失败"; "ask_me_anything" = "向我提问"; +"asset_changes" = "资产变动"; +"asset_changes_estimate" = "资产变动(预估)"; "asset_key" = "资产标识"; "asset_not_found" = "找不到该资产"; "asset_type" = "资产类型"; @@ -1197,7 +1199,6 @@ "suspended_deposit" = "%1$@ 充值已暂停,可能的原因是因为使用率太低、节点故障、区块链网络硬分叉或者拥堵,注意已转入的 %2$@ 资产可以继续转账。"; "suspicious_link" = "可疑链接"; "swap" = "交易"; -"swap_asset_change" = "资产变动(预估)"; "swap_confirmation" = "确认交易"; "swap_failed" = "交易失败"; "swap_invalid_amount" = "没有报价。请尝试修改金额。"; diff --git a/Mixin/Resources/zh-Hant.lproj/Localizable.strings b/Mixin/Resources/zh-Hant.lproj/Localizable.strings index ea95f21686..7a91a2d161 100644 --- a/Mixin/Resources/zh-Hant.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hant.lproj/Localizable.strings @@ -146,6 +146,8 @@ "approving_transaction" = "正在批准"; "approving_transaction_failed" = "批准失敗"; "ask_me_anything" = "向我提問"; +"asset_changes" = "資產變動"; +"asset_changes_estimate" = "資產變動(預估)"; "asset_key" = "資產標識"; "asset_not_found" = "找不到該資產"; "asset_type" = "資產型別"; @@ -1197,7 +1199,6 @@ "suspended_deposit" = "%1$@ 充值已暫停,可能的原因是因為使用率太低、節點故障、區塊鏈網路硬分叉或者擁堵,注意已轉入的 %2$@ 資產可以繼續轉賬。"; "suspicious_link" = "可疑連結"; "swap" = "交易"; -"swap_asset_change" = "資產變動(預估)"; "swap_confirmation" = "確認交易"; "swap_failed" = "交易失敗"; "swap_invalid_amount" = "沒有報價。請嘗試修改金額。"; diff --git a/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewHeaderView.swift b/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewHeaderView.swift index 57bb2c55bb..4c5e249936 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewHeaderView.swift +++ b/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/AuthenticationPreviewHeaderView.swift @@ -12,7 +12,7 @@ final class AuthenticationPreviewHeaderView: UIView { private weak var assetIconView: BadgeIconView? private weak var progressView: AuthenticationProgressView? private weak var textContentView: TextInscriptionContentView? - private weak var swapIconView: SwapIconView? + private weak var multipleTokenIconView: MultipleTokenIconView? override func awakeFromNib() { super.awakeFromNib() @@ -58,25 +58,43 @@ final class AuthenticationPreviewHeaderView: UIView { } func setIcon(sendToken: TokenItem, receiveToken: SwapToken) { - let iconView: SwapIconView - if let view = self.swapIconView, view.isDescendant(of: iconWrapperView) { + let iconView: MultipleTokenIconView + if let view = self.multipleTokenIconView, view.isDescendant(of: iconWrapperView) { iconView = view } else { for iconView in iconWrapperView.subviews { iconView.removeFromSuperview() } - iconView = SwapIconView() + iconView = MultipleTokenIconView() iconWrapperView.addSubview(iconView) iconView.snp.makeConstraints { make in make.center.equalToSuperview() - make.width.equalTo(124) make.height.equalTo(70) } - self.swapIconView = iconView + self.multipleTokenIconView = iconView } iconView.setIcon(sendToken: sendToken, receiveToken: receiveToken) } + func setIcon(tokens: [TokenItem]) { + let iconView: MultipleTokenIconView + if let view = self.multipleTokenIconView, view.isDescendant(of: iconWrapperView) { + iconView = view + } else { + for iconView in iconWrapperView.subviews { + iconView.removeFromSuperview() + } + iconView = MultipleTokenIconView() + iconWrapperView.addSubview(iconView) + iconView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.height.equalTo(70) + } + self.multipleTokenIconView = iconView + } + iconView.setIcons(tokens: tokens) + } + func setIcon(progress: AuthenticationProgressView.Progress) { let progressView: AuthenticationProgressView if let view = self.progressView, view.isDescendant(of: iconWrapperView) { @@ -137,12 +155,15 @@ final class AuthenticationPreviewHeaderView: UIView { extension AuthenticationPreviewHeaderView { - private final class SwapIconView: UIView { + private final class MultipleTokenIconView: UIView { + + private typealias IconWrapperView = StackedIconWrapperView + + private let stackView = UIStackView() - private let sendIconView = UIImageView() - private let borderProviderView = UIView() - private let receiveIconView = UIImageView() - private let borderWidth: CGFloat = 2 + private var wrapperViews: [IconWrapperView] = [] + + private weak var addtionalCountLabel: UILabel? override init(frame: CGRect) { super.init(frame: frame) @@ -154,48 +175,80 @@ extension AuthenticationPreviewHeaderView { loadSubviews() } - override func layoutSubviews() { - super.layoutSubviews() - sendIconView.layer.cornerRadius = sendIconView.frame.width / 2 - borderProviderView.layer.cornerRadius = borderProviderView.frame.width / 2 - receiveIconView.layer.cornerRadius = receiveIconView.frame.width / 2 + func setIcons(tokens: [TokenItem]) { + if tokens.count > 3 { + loadIconViews(count: 2) { _, wrapperView in + wrapperView.snp.makeConstraints { make in + make.width.equalTo(wrapperView.snp.height).offset(-16) + } + } + let label: UILabel + if let l = addtionalCountLabel { + label = l + } else { + let view = StackedIconWrapperView() + view.backgroundColor = .clear + label = view.iconView + label.backgroundColor = R.color.background_quaternary() + label.textColor = R.color.text_tertiary() + label.font = .systemFont(ofSize: 20) + label.textAlignment = .center + label.minimumScaleFactor = 0.1 + label.layer.cornerRadius = 34 + label.layer.masksToBounds = true + stackView.addArrangedSubview(view) + view.snp.makeConstraints { make in + make.size.equalTo(70) + } + } + label.text = "+\(tokens.count - 2)" + } else { + loadIconViews(count: tokens.count) { index, wrapperView in + let offset = index == tokens.count - 1 ? 0 : -16 + wrapperView.snp.makeConstraints { make in + make.width.equalTo(wrapperView.snp.height).offset(offset) + } + } + } + for (i, wrapperView) in wrapperViews.enumerated() { + wrapperView.iconView.setIcon(token: tokens[i]) + } } func setIcon(sendToken: TokenItem, receiveToken: SwapToken) { - sendIconView.sd_setImage(with: URL(string: sendToken.iconURL), - placeholderImage: nil, - context: assetIconContext) - receiveIconView.sd_setImage(with: receiveToken.iconURL, - placeholderImage: nil, - context: assetIconContext) + loadIconViews(count: 2) { index, wrapperView in + let offset = index == 1 ? 0 : -16 + wrapperView.snp.makeConstraints { make in + make.width.equalTo(wrapperView.snp.height).offset(offset) + } + } + wrapperViews[0].iconView.setIcon(token: sendToken) + wrapperViews[1].iconView.setIcon(token: receiveToken) } - private func loadSubviews() { - sendIconView.layer.masksToBounds = true - addSubview(sendIconView) - sendIconView.snp.makeConstraints { make in - make.top.leading.equalToSuperview().offset(borderWidth) - make.bottom.equalToSuperview().offset(-borderWidth) - make.width.equalTo(sendIconView.snp.height) + private func loadIconViews(count: Int, makeConstraints maker: (Int, IconWrapperView) -> Void) { + guard wrapperViews.count != count else { + return } - - borderProviderView.backgroundColor = R.color.background() - borderProviderView.layer.masksToBounds = true - addSubview(borderProviderView) - borderProviderView.snp.makeConstraints { make in - make.top.trailing.bottom.equalToSuperview() - make.width.equalTo(borderProviderView.snp.height) + for view in stackView.arrangedSubviews { + view.removeFromSuperview() } - - receiveIconView.layer.masksToBounds = true - addSubview(receiveIconView) - receiveIconView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(borderWidth) - make.trailing.bottom.equalToSuperview().offset(-borderWidth) - make.width.equalTo(receiveIconView.snp.height) + wrapperViews = [] + for i in 0.. 0 { + for view in rowViews.suffix(diff) { + view.removeFromSuperview() + } + rowViews.removeLast(diff) + } else if diff < 0 { + for _ in (0 ..< -diff) { + let view = RowStackView() + rowViews.append(view) + contentStackView.addArrangedSubview(view) + } + } + } + +} + +extension MultipleAssetChangeCell { + private class RowStackView: UIStackView { let iconView = PlainTokenIconView(frame: CGRect(x: 0, y: 0, width: 24, height: 24)) diff --git a/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/SwapAssetChangeCell.xib b/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/MultipleAssetChangeCell.xib similarity index 89% rename from Mixin/UserInterface/Controllers/Wallet/Authentication Preview/SwapAssetChangeCell.xib rename to Mixin/UserInterface/Controllers/Wallet/Authentication Preview/MultipleAssetChangeCell.xib index 4bf2754a55..3b7422ed90 100644 --- a/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/SwapAssetChangeCell.xib +++ b/Mixin/UserInterface/Controllers/Wallet/Authentication Preview/MultipleAssetChangeCell.xib @@ -1,23 +1,23 @@ - + - + - + - +