From e85188aabb47195c06f801115144bcee58033aca Mon Sep 17 00:00:00 2001 From: romchornyi <270373998+romchornyi@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:49:00 +0300 Subject: [PATCH 1/6] feat: add remaining components (EnterAmount, CoinSelector, Converter, TopIntro, Transaction) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final migration PR — brings the rest of the library across: - CoinSelector, NumericKeyboardView, ReceiveEstimateView - EnterAmount suite (CurrencyOption, DashBalanceView, DashPickerView, DualSwapAmountView, EnterAmountView, SwapAmountView) - ConverterCard suite (ConverterArrowBadge, ConverterCard, ConverterCardItem, ConverterCardRow) - TopIntro/TopIntroView, Transaction/TransactionView - Foundation: DashTextStyle + Font (caption1Medium) - assets: arrow-down, diagonal-up-down, stopwatch, Transactions/, text-field-qr dark variant iOS 14 compatibility fixes applied during migration: - replaced iOS 16+ .rect(cornerRadius:) / .contentShape(.rect) with RoundedRectangle(...,style:.continuous) / Rectangle() (CoinSelector, DashPickerView, ConverterCardRow, TransactionView) - added missing @available(iOS 14, macOS 11, *) to ReceiveEstimateView, CurrencyOption, TopIntroView and their preview scaffolding --- .../DashUIKit/Components/CoinSelector.swift | 155 +++++ .../ConverterCard/ConverterArrowBadge.swift | 63 ++ .../ConverterCard/ConverterCard.swift | 229 +++++++ .../ConverterCard/ConverterCardItem.swift | 75 ++ .../ConverterCard/ConverterCardRow.swift | 63 ++ .../EnterAmount/CurrencyOption.swift | 70 ++ .../EnterAmount/DashBalanceView.swift | 76 +++ .../EnterAmount/DashPickerView.swift | 77 +++ .../EnterAmount/DualSwapAmountView.swift | 121 ++++ .../EnterAmount/EnterAmountView.swift | 368 ++++++++++ .../EnterAmount/SwapAmountView.swift | 639 ++++++++++++++++++ .../Components/NumericKeyboardView.swift | 241 +++++++ .../Components/ReceiveEstimateView.swift | 108 +++ .../Components/TopIntro/TopIntroView.swift | 60 ++ .../Transaction/TransactionView.swift | 260 +++++++ .../DashUIKit/Foundation/DashTextStyle.swift | 1 + .../DashUIKit/Foundation/Font+DashUI.swift | 3 + .../text-field-qr.imageset/Contents.json | 33 + .../text-field-qr-dark.png | Bin 0 -> 471 bytes .../text-field-qr-dark@2x.png | Bin 0 -> 734 bytes .../text-field-qr-dark@3x.png | Bin 0 -> 1048 bytes .../Transactions/AdditionalInfo/Contents.json | 6 + .../Contents.json | 56 ++ .../additional-info-error-dark.png | Bin 0 -> 473 bytes .../additional-info-error-dark@2x.png | Bin 0 -> 728 bytes .../additional-info-error-dark@3x.png | Bin 0 -> 1086 bytes .../additional-info-error.png | Bin 0 -> 463 bytes .../additional-info-error@2x.png | Bin 0 -> 741 bytes .../additional-info-error@3x.png | Bin 0 -> 1137 bytes .../Contents.json | 56 ++ .../additional-info-gift-card-dark.png | Bin 0 -> 519 bytes .../additional-info-gift-card-dark@2x.png | Bin 0 -> 862 bytes .../additional-info-gift-card-dark@3x.png | Bin 0 -> 1238 bytes .../additional-info-gift-card.png | Bin 0 -> 508 bytes .../additional-info-gift-card@2x.png | Bin 0 -> 851 bytes .../additional-info-gift-card@3x.png | Bin 0 -> 1231 bytes .../Contents.json | 56 ++ .../additional-info-received-dark.png | Bin 0 -> 459 bytes .../additional-info-received-dark@2x.png | Bin 0 -> 715 bytes .../additional-info-received-dark@3x.png | Bin 0 -> 1017 bytes .../additional-info-received.png | Bin 0 -> 465 bytes .../additional-info-received@2x.png | Bin 0 -> 723 bytes .../additional-info-received@3x.png | Bin 0 -> 1023 bytes .../Contents.json | 56 ++ .../additional-info-sent-dark.png | Bin 0 -> 443 bytes .../additional-info-sent-dark@2x.png | Bin 0 -> 658 bytes .../additional-info-sent-dark@3x.png | Bin 0 -> 972 bytes .../additional-info-sent.png | Bin 0 -> 443 bytes .../additional-info-sent@2x.png | Bin 0 -> 658 bytes .../additional-info-sent@3x.png | Bin 0 -> 977 bytes .../Transactions/Contents.json | 6 + .../Transactions/Preview/Contents.json | 6 + .../Contents.json | 56 ++ .../transaction-all.trans-dark.png | Bin 0 -> 691 bytes .../transaction-all.trans-dark@2x.png | Bin 0 -> 1133 bytes .../transaction-all.trans-dark@3x.png | Bin 0 -> 1629 bytes .../transaction-all.trans.png | Bin 0 -> 695 bytes .../transaction-all.trans@2x.png | Bin 0 -> 1135 bytes .../transaction-all.trans@3x.png | Bin 0 -> 1629 bytes .../Contents.json | 56 ++ .../transaction-coinbase.received-dark.png | Bin 0 -> 617 bytes .../transaction-coinbase.received-dark@2x.png | Bin 0 -> 1093 bytes .../transaction-coinbase.received-dark@3x.png | Bin 0 -> 1627 bytes .../transaction-coinbase.received.png | Bin 0 -> 617 bytes .../transaction-coinbase.received@2x.png | Bin 0 -> 1093 bytes .../transaction-coinbase.received@3x.png | Bin 0 -> 1627 bytes .../Contents.json | 56 ++ ...ansaction-contact-request-approve-dark.png | Bin 0 -> 808 bytes ...action-contact-request-approve-dark@2x.png | Bin 0 -> 1436 bytes ...action-contact-request-approve-dark@3x.png | Bin 0 -> 2123 bytes .../transaction-contact-request-approve.png | Bin 0 -> 832 bytes ...transaction-contact-request-approve@2x.png | Bin 0 -> 1439 bytes ...transaction-contact-request-approve@3x.png | Bin 0 -> 2097 bytes .../Contents.json | 56 ++ .../transaction-contact-request-sent-dark.png | Bin 0 -> 737 bytes ...ansaction-contact-request-sent-dark@2x.png | Bin 0 -> 1312 bytes ...ansaction-contact-request-sent-dark@3x.png | Bin 0 -> 1945 bytes .../transaction-contact-request-sent.png | Bin 0 -> 736 bytes .../transaction-contact-request-sent@2x.png | Bin 0 -> 1336 bytes .../transaction-contact-request-sent@3x.png | Bin 0 -> 1916 bytes .../Contents.json | 56 ++ .../transaction-convert-dark.png | Bin 0 -> 865 bytes .../transaction-convert-dark@2x.png | Bin 0 -> 1549 bytes .../transaction-convert-dark@3x.png | Bin 0 -> 2158 bytes .../transaction-convert.png | Bin 0 -> 870 bytes .../transaction-convert@2x.png | Bin 0 -> 1539 bytes .../transaction-convert@3x.png | Bin 0 -> 2180 bytes .../Contents.json | 56 ++ .../transaction-crowdnode-dark.png | Bin 0 -> 783 bytes .../transaction-crowdnode-dark@2x.png | Bin 0 -> 1539 bytes .../transaction-crowdnode-dark@3x.png | Bin 0 -> 2184 bytes .../transaction-crowdnode.png | Bin 0 -> 783 bytes .../transaction-crowdnode@2x.png | Bin 0 -> 1539 bytes .../transaction-crowdnode@3x.png | Bin 0 -> 2184 bytes .../transaction-error.imageset/Contents.json | 56 ++ .../transaction-error-dark.png | Bin 0 -> 749 bytes .../transaction-error-dark@2x.png | Bin 0 -> 1310 bytes .../transaction-error-dark@3x.png | Bin 0 -> 1769 bytes .../transaction-error.png | Bin 0 -> 726 bytes .../transaction-error@2x.png | Bin 0 -> 1332 bytes .../transaction-error@3x.png | Bin 0 -> 1765 bytes .../Contents.json | 56 ++ .../transaction-gift-card-dark.png | Bin 0 -> 867 bytes .../transaction-gift-card-dark@2x.png | Bin 0 -> 1403 bytes .../transaction-gift-card-dark@3x.png | Bin 0 -> 2088 bytes .../transaction-gift-card.png | Bin 0 -> 867 bytes .../transaction-gift-card@2x.png | Bin 0 -> 1403 bytes .../transaction-gift-card@3x.png | Bin 0 -> 2088 bytes .../Contents.json | 56 ++ .../transaction-internal-transfer-dark.png | Bin 0 -> 829 bytes .../transaction-internal-transfer-dark@2x.png | Bin 0 -> 1392 bytes .../transaction-internal-transfer-dark@3x.png | Bin 0 -> 1994 bytes .../transaction-internal-transfer.png | Bin 0 -> 804 bytes .../transaction-internal-transfer@2x.png | Bin 0 -> 1359 bytes .../transaction-internal-transfer@3x.png | Bin 0 -> 1943 bytes .../transaction-mining.imageset/Contents.json | 56 ++ .../transaction-mining-dark.png | Bin 0 -> 716 bytes .../transaction-mining-dark@2x.png | Bin 0 -> 1195 bytes .../transaction-mining-dark@3x.png | Bin 0 -> 1763 bytes .../transaction-mining.png | Bin 0 -> 697 bytes .../transaction-mining@2x.png | Bin 0 -> 1162 bytes .../transaction-mining@3x.png | Bin 0 -> 1704 bytes .../transaction-mixing.imageset/Contents.json | 56 ++ .../transaction-mixing-dark.png | Bin 0 -> 867 bytes .../transaction-mixing-dark@2x.png | Bin 0 -> 1565 bytes .../transaction-mixing-dark@3x.png | Bin 0 -> 2224 bytes .../transaction-mixing.png | Bin 0 -> 875 bytes .../transaction-mixing@2x.png | Bin 0 -> 1565 bytes .../transaction-mixing@3x.png | Bin 0 -> 2248 bytes .../Contents.json | 56 ++ .../transaction-received-dark.png | Bin 0 -> 724 bytes .../transaction-received-dark@2x.png | Bin 0 -> 1163 bytes .../transaction-received-dark@3x.png | Bin 0 -> 1764 bytes .../transaction-received.png | Bin 0 -> 727 bytes .../transaction-received@2x.png | Bin 0 -> 1167 bytes .../transaction-received@3x.png | Bin 0 -> 1757 bytes .../transaction-sent.imageset/Contents.json | 56 ++ .../transaction-sent-dark.png | Bin 0 -> 661 bytes .../transaction-sent-dark@2x.png | Bin 0 -> 1091 bytes .../transaction-sent-dark@3x.png | Bin 0 -> 1638 bytes .../transaction-sent.png | Bin 0 -> 664 bytes .../transaction-sent@2x.png | Bin 0 -> 1072 bytes .../transaction-sent@3x.png | Bin 0 -> 1636 bytes .../Contents.json | 56 ++ .../transaction-uphold.received-dark.png | Bin 0 -> 874 bytes .../transaction-uphold.received-dark@2x.png | Bin 0 -> 1631 bytes .../transaction-uphold.received-dark@3x.png | Bin 0 -> 2414 bytes .../transaction-uphold.received.png | Bin 0 -> 874 bytes .../transaction-uphold.received@2x.png | Bin 0 -> 1623 bytes .../transaction-uphold.received@3x.png | Bin 0 -> 2414 bytes .../arrow-down.imageset/Contents.json | 23 + .../arrow-down.imageset/arrow-down.png | Bin 0 -> 283 bytes .../arrow-down.imageset/arrow-down@2x.png | Bin 0 -> 349 bytes .../arrow-down.imageset/arrow-down@3x.png | Bin 0 -> 462 bytes .../diagonal-up-down.imageset/Contents.json | 23 + .../diagonal-up-down.png | Bin 0 -> 305 bytes .../diagonal-up-down@2x.png | Bin 0 -> 393 bytes .../diagonal-up-down@3x.png | Bin 0 -> 579 bytes .../stopwatch.imageset/Contents.json | 23 + .../stopwatch.imageset/stopwatch.png | Bin 0 -> 497 bytes .../stopwatch.imageset/stopwatch@2x.png | Bin 0 -> 853 bytes .../stopwatch.imageset/stopwatch@3x.png | Bin 0 -> 1154 bytes 162 files changed, 3737 insertions(+) create mode 100644 Sources/DashUIKit/Components/CoinSelector.swift create mode 100644 Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift create mode 100644 Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift create mode 100644 Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift create mode 100644 Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift create mode 100644 Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift create mode 100644 Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift create mode 100644 Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift create mode 100644 Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift create mode 100644 Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift create mode 100644 Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift create mode 100644 Sources/DashUIKit/Components/NumericKeyboardView.swift create mode 100644 Sources/DashUIKit/Components/ReceiveEstimateView.swift create mode 100644 Sources/DashUIKit/Components/TopIntro/TopIntroView.swift create mode 100644 Sources/DashUIKit/Components/Transaction/TransactionView.swift create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Other/text-field-qr.imageset/text-field-qr-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Other/text-field-qr.imageset/text-field-qr-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Other/text-field-qr.imageset/text-field-qr-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/additional-info-error-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/additional-info-error-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/additional-info-error-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/additional-info-error.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/additional-info-error@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-error.imageset/additional-info-error@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/additional-info-gift-card-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/additional-info-gift-card-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/additional-info-gift-card-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/additional-info-gift-card.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/additional-info-gift-card@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-gift-card.imageset/additional-info-gift-card@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/additional-info-received-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/additional-info-received-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/additional-info-received-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/additional-info-received.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/additional-info-received@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-received.imageset/additional-info-received@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/additional-info-sent-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/additional-info-sent-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/additional-info-sent-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/additional-info-sent.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/additional-info-sent@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/AdditionalInfo/additional-info-sent.imageset/additional-info-sent@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/transaction-all.trans-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/transaction-all.trans-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/transaction-all.trans-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/transaction-all.trans.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/transaction-all.trans@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-all.trans.imageset/transaction-all.trans@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/transaction-coinbase.received-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/transaction-coinbase.received-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/transaction-coinbase.received-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/transaction-coinbase.received.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/transaction-coinbase.received@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-coinbase.received.imageset/transaction-coinbase.received@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/transaction-contact-request-approve-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/transaction-contact-request-approve-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/transaction-contact-request-approve-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/transaction-contact-request-approve.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/transaction-contact-request-approve@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-approve.imageset/transaction-contact-request-approve@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/transaction-contact-request-sent-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/transaction-contact-request-sent-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/transaction-contact-request-sent-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/transaction-contact-request-sent.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/transaction-contact-request-sent@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-contact-request-sent.imageset/transaction-contact-request-sent@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/transaction-convert-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/transaction-convert-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/transaction-convert-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/transaction-convert.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/transaction-convert@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-convert.imageset/transaction-convert@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/transaction-crowdnode-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/transaction-crowdnode-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/transaction-crowdnode-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/transaction-crowdnode.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/transaction-crowdnode@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-crowdnode.imageset/transaction-crowdnode@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/transaction-error-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/transaction-error-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/transaction-error-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/transaction-error.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/transaction-error@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-error.imageset/transaction-error@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/transaction-gift-card-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/transaction-gift-card-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/transaction-gift-card-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/transaction-gift-card.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/transaction-gift-card@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-gift-card.imageset/transaction-gift-card@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/transaction-internal-transfer-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/transaction-internal-transfer-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/transaction-internal-transfer-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/transaction-internal-transfer.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/transaction-internal-transfer@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-internal-transfer.imageset/transaction-internal-transfer@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/transaction-mining-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/transaction-mining-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/transaction-mining-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/transaction-mining.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/transaction-mining@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mining.imageset/transaction-mining@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/transaction-mixing-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/transaction-mixing-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/transaction-mixing-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/transaction-mixing.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/transaction-mixing@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-mixing.imageset/transaction-mixing@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/transaction-received-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/transaction-received-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/transaction-received-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/transaction-received.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/transaction-received@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-received.imageset/transaction-received@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/transaction-sent-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/transaction-sent-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/transaction-sent-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/transaction-sent.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/transaction-sent@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-sent.imageset/transaction-sent@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/transaction-uphold.received-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/transaction-uphold.received-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/transaction-uphold.received-dark@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/transaction-uphold.received.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/transaction-uphold.received@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Transactions/Preview/transaction-uphold.received.imageset/transaction-uphold.received@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/arrow-down.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/arrow-down.imageset/arrow-down.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/arrow-down.imageset/arrow-down@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/arrow-down.imageset/arrow-down@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/diagonal-up-down.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/diagonal-up-down.imageset/diagonal-up-down.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/diagonal-up-down.imageset/diagonal-up-down@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/diagonal-up-down.imageset/diagonal-up-down@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/stopwatch.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/stopwatch.imageset/stopwatch.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/stopwatch.imageset/stopwatch@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/stopwatch.imageset/stopwatch@3x.png diff --git a/Sources/DashUIKit/Components/CoinSelector.swift b/Sources/DashUIKit/Components/CoinSelector.swift new file mode 100644 index 0000000..7ce6ec4 --- /dev/null +++ b/Sources/DashUIKit/Components/CoinSelector.swift @@ -0,0 +1,155 @@ +// +// Created by Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@available(iOS 14, macOS 11, *) +public enum CoinSelectorTrailing { + case price(String) + case halted +} + +private enum CoinSelectorLayout { + static let spacing: CGFloat = 16 + static let textSpacing: CGFloat = 1 + static let padding: CGFloat = 10 + static let badgeHPadding: CGFloat = 8 + static let badgeVPadding: CGFloat = 2 + static let badgeCornerRadius: CGFloat = 6 +} + +@available(iOS 14, macOS 11, *) +public struct CoinSelector: View { + + private let name: String + private let code: String + private let network: String? + private let trailing: CoinSelectorTrailing? + private let icon: Icon + + public init( + name: String, + code: String, + network: String? = nil, + trailing: CoinSelectorTrailing? = nil, + @ViewBuilder icon: () -> Icon + ) { + self.name = name + self.code = code + self.network = network + self.trailing = trailing + self.icon = icon() + } + + private var isHalted: Bool { + if case .halted = trailing { return true } + return false + } + + public var body: some View { + HStack(spacing: CoinSelectorLayout.spacing) { + icon + info + trailingView + } + .padding(CoinSelectorLayout.padding) + .opacity(isHalted ? 0.5 : 1.0) + .contentShape(Rectangle()) + } + + private var info: some View { + VStack(alignment: .leading, spacing: CoinSelectorLayout.textSpacing) { + Text(name) + .dashFont(.subheadMedium) + .foregroundColor(Color.dash.primaryText) + .lineLimit(1) + .truncationMode(.tail) + Text(code) + .dashFont(.footnote) + .foregroundColor(Color.dash.tertiaryText) + .lineLimit(1) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + + @ViewBuilder + private var trailingView: some View { + if case .halted = trailing { + haltedLabel + } else { + VStack(alignment: .trailing, spacing: 0) { + if case .price(let value) = trailing { + Text(value) + .dashFont(.caption1) + .foregroundColor(Color.dash.primaryText) + .lineLimit(1) + } + if let network { + Text(network) + .dashFont(.caption1) + .foregroundColor(Color.dash.tertiaryText) + .lineLimit(1) + .fixedSize() + } + } + } + } + + private var haltedLabel: some View { + Text(NSLocalizedString("halted", bundle: .module, comment: "DashUIKit")) + .dashFont(.caption1) + .foregroundColor(Color.dash.tertiaryText) + .padding(.horizontal, CoinSelectorLayout.badgeHPadding) + .padding(.vertical, CoinSelectorLayout.badgeVPadding) + .background(Color.dash.black1000Alpha8) + .clipShape(RoundedRectangle(cornerRadius: CoinSelectorLayout.badgeCornerRadius, style: .continuous)) + } +} + +#if DEBUG + +@available(iOS 14, macOS 11, *) +struct CoinSelector_Previews: PreviewProvider { + + static var iconPlaceholder: some View { + RoundedRectangle(cornerRadius: 6) + .fill(Color.dash.blue) + .frame(width: 30, height: 30) + } + + static var previews: some View { + VStack(spacing: 0) { + CoinSelector(name: "Rune", code: "RUNE", network: "Maya", trailing: .price("$0.40")) { + iconPlaceholder + } + CoinSelector(name: "Ethereum", code: "ETH", network: "NEAR", trailing: .price("$3,200.00")) { + iconPlaceholder + } + CoinSelector(name: "USD Coin", code: "USDC", network: "Multiple", trailing: .price("$1.00")) { + iconPlaceholder + } + CoinSelector(name: "Tether", code: "USDT", trailing: .halted) { + iconPlaceholder + } + CoinSelector(name: "Bitcoin", code: "BTC") { + iconPlaceholder + } + } + .previewLayout(.sizeThatFits) + } +} + +#endif diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift new file mode 100644 index 0000000..31168fa --- /dev/null +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift @@ -0,0 +1,63 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - ConverterArrowBadge + +/// The arrow badge centered on the seam between the two `ConverterCard` rows. +/// +/// - `onSwap == nil` → static `arrow-down` icon, non-interactive. +/// - `onSwap != nil` → tappable `diagonal-up-down` button that rotates 180° on each tap and fires +/// the swap action. +@available(iOS 14, macOS 11, *) +struct ConverterArrowBadge: View { + let onSwap: (() -> Void)? + @State private var rotation: Double = 0 + + var body: some View { + Group { + if let onSwap { + Button { + withAnimation(.spring(response: 0.35, dampingFraction: 0.82)) { rotation += 180 } + onSwap() + } label: { + badge(iconName: "diagonal-up-down", iconRotation: rotation) + } + .buttonStyle(.plain) + } else { + badge(iconName: "arrow-down", iconRotation: 0) + } + } + .frame(height: 35) + .padding(.horizontal, 10) + } + + private func badge(iconName: String, iconRotation: Double) -> some View { + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color.dash.secondaryBackground) + .frame(width: 35, height: 35) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .stroke(Color.dash.primaryBackground, lineWidth: 5) + ) + .overlay( + Image(dash: .custom(iconName, bundle: .dashUIKit)) + .rotationEffect(.degrees(iconRotation)) + ) + } +} diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift new file mode 100644 index 0000000..79bbd41 --- /dev/null +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift @@ -0,0 +1,229 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - ConverterCard + +/// A two-row card (source → destination) with an arrow badge centered on the seam between the +/// rows. Each row is a `MenuItem` wrapped in rounded card chrome. +/// +/// Pass `onSwap` to show a tappable `diagonal-up-down` button; omit it (or pass `nil`) for a +/// static `arrow-down` indicator when the card is non-swappable. +@available(iOS 14, macOS 11, *) +public struct ConverterCard: View { + + private enum Layout { + static let cardSpacing: CGFloat = 5 + } + + private let fromItem: ConverterCardItem + private let toItem: ConverterCardItem + private let onSwap: (() -> Void)? + + @State private var topRowHeight: CGFloat = 74 + + public init( + fromItem: ConverterCardItem, + toItem: ConverterCardItem, + onSwap: (() -> Void)? = nil + ) { + self.fromItem = fromItem + self.toItem = toItem + self.onSwap = onSwap + } + + private var orderedItems: [ConverterCardItem] { [fromItem, toItem] } + + public var body: some View { + VStack(spacing: Layout.cardSpacing) { + ForEach(Array(orderedItems.enumerated()), id: \.element.id) { index, item in + ConverterCardRow(slot: index == 0 ? .top : .bottom) { + row(item: item) + } + } + } + .animation(.spring(response: 0.35, dampingFraction: 0.82), value: fromItem.id) + .overlay( + ConverterArrowBadge(onSwap: onSwap) + // Anchor the badge's CENTER to the overlay's .top point, so the offset below + // places the badge centre exactly on the seam — independent of the badge's height. + .alignmentGuide(VerticalAlignment.top) { $0[VerticalAlignment.center] } + .offset(y: seamY), + alignment: .top + ) + .onPreferenceChange(ConverterRowHeightKey.self) { heights in + if let h = heights[.top], h > 0 { topRowHeight = h } + } + } + + /// Centre of the gap between the two rows. Depends only on the top row height and the stack + /// spacing — never on the bottom row — so it stays correct when the rows differ in height. + private var seamY: CGFloat { topRowHeight + Layout.cardSpacing / 2 } + + /// Row layout mirrors `MenuItem` (icon 30pt, 10pt padding) but stays flexible enough for a + /// custom icon/trailing view and a multi-line subtitle. + private func row(item: ConverterCardItem) -> some View { + HStack(spacing: 10) { + leading(item) + + VStack(alignment: .leading, spacing: 1) { + Text(item.title) + .dashFont(.subheadMedium) + .foregroundColor(Color.dash.primaryText) + + if let subtitle = item.subtitle { + Text(subtitle) + .dashFont(.footnote) + .foregroundColor(Color.dash.secondaryText) + .lineLimit(item.subtitleLineLimit) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding(.leading, 6) + + Spacer(minLength: 8) + + trailing(item) + } + .padding(10) + } + + @ViewBuilder + private func leading(_ item: ConverterCardItem) -> some View { + if let iconView = item.iconView { + iconView.frame(width: 30, height: 30) + } else if let icon = item.icon { + Image(dash: icon) + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + } + } + + @ViewBuilder + private func trailing(_ item: ConverterCardItem) -> some View { + if let trailingView = item.trailingView { + trailingView + } else if item.showsBalance { + VStack(alignment: .trailing, spacing: 1) { + DashAmount(amount: item.dashBalance, sign: .none) + .foregroundColor(Color.dash.primaryText) + + if item.dashBalance != 0, let fiat = item.fiat { + Text(fiat) + .dashFont(.footnote) + .foregroundColor(Color.dash.secondaryText) + } + } + } + } +} + +#if DEBUG +@available(iOS 14, macOS 11, *) +struct ConverterCard_Previews: PreviewProvider { + static var previews: some View { + Group { + ConverterCard( + fromItem: ConverterCardItem( + icon: .system("bitcoinsign.circle.fill"), + title: "Coinbase", + subtitle: "Dash Wallet", + dashBalance: 420_000_000, + fiat: "$410.00" + ), + toItem: ConverterCardItem( + icon: .system("d.circle.fill"), + title: "Dash", + subtitle: "Dash Wallet", + dashBalance: 123_456_789, + fiat: "$120.00", + showsBalance: false + ), + onSwap: {} + ) + .previewDisplayName("Swappable") + + ConverterCard( + fromItem: ConverterCardItem( + icon: .system("d.circle.fill"), + title: "Dash", + dashBalance: 500_000_000, + fiat: "$490.00" + ), + toItem: ConverterCardItem( + icon: .system("arrow.right.circle.fill"), + title: "Destination", + showsBalance: false + ) + ) + .previewDisplayName("Static (no swap)") + + // Unequal rows — badge must stay on the seam when the bottom row is taller. + ConverterCard( + fromItem: ConverterCardItem( + icon: .system("bitcoinsign.circle.fill"), + title: "Coinbase", + dashBalance: 420_000_000, + fiat: "$410.00" + ), + toItem: ConverterCardItem( + icon: .system("d.circle.fill"), + title: "Dash", + subtitle: "XqK7pMn2rV9wL4tYeB8dN3cF6hJsA1uZ — long address wraps and makes this row taller than the top row", + subtitleLineLimit: nil, + showsBalance: false + ), + onSwap: {} + ) + .previewDisplayName("Unequal rows (tall bottom)") + } + .padding() + .background(Color.dash.primaryBackground) + } +} + +@available(iOS 14, macOS 11, *) +struct SwapPreview: View { + @State private var swapped = false + + var coinbase: ConverterCardItem { + ConverterCardItem(id: "coinbase", icon: .system("bitcoinsign.circle.fill"), + title: "Coinbase", dashBalance: 131_715_000, fiat: "$0.04") + } + var dash: ConverterCardItem { + ConverterCardItem(id: "dash", icon: .system("d.circle.fill"), + title: "Dash", subtitle: "Dash Wallet", + dashBalance: 17_351_000, fiat: "$6.04") + } + + var body: some View { + ConverterCard( + fromItem: swapped ? dash : coinbase, + toItem: swapped ? coinbase : dash, + onSwap: { swapped.toggle() } + ) + .padding() + .background(Color.dash.primaryBackground) + } +} + +@available(iOS 17, macOS 14, *) +#Preview("Swap animation") { SwapPreview() } +#endif diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift new file mode 100644 index 0000000..1dd5519 --- /dev/null +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift @@ -0,0 +1,75 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - ConverterCardItem + +/// Display data for a single row of `ConverterCard`. +/// +/// The common case (a `DashIconSource` icon + a Dash balance) is fully described by the plain +/// values. For richer rows — a remotely-loaded icon, a custom trailing view, or a multi-line +/// subtitle — pass `iconView` / `trailingView` / `subtitleLineLimit`, which take precedence. +/// +/// `id` drives the swap slide animation: it must be **stable per data-source across rebuilds** +/// (the Coinbase row keeps the same id whether it occupies the top or bottom slot). The default +/// `id = title` is sufficient when titles are distinct and stable. Pass an explicit `id` if the +/// title might change or if two rows could share a title. +@available(iOS 14, macOS 11, *) +public struct ConverterCardItem: Identifiable { + public let id: AnyHashable + /// Static leading icon. Ignored when `iconView` is non-nil. + public let icon: DashIconSource? + /// Custom leading view (e.g. a remotely-loaded coin icon). Takes precedence over `icon`. + public let iconView: AnyView? + public let title: String + public let subtitle: String? + /// Subtitle line cap; `nil` allows unlimited wrapping (e.g. a long address). + public let subtitleLineLimit: Int? + /// Dash balance in duffs (10⁸ per Dash) — rendered as a Dash amount when `showsBalance`. + public let dashBalance: Int64 + public let fiat: String? + /// Custom trailing view (e.g. a formatted `DashBalanceView`). Takes precedence over the + /// built-in dash-balance rendering. + public let trailingView: AnyView? + /// When `false` (and no `trailingView`), the row hides its trailing balance. + public let showsBalance: Bool + + public init( + id: AnyHashable? = nil, + icon: DashIconSource? = nil, + iconView: AnyView? = nil, + title: String, + subtitle: String? = nil, + subtitleLineLimit: Int? = 1, + dashBalance: Int64 = 0, + fiat: String? = nil, + trailingView: AnyView? = nil, + showsBalance: Bool = true + ) { + self.id = id ?? AnyHashable(title) + self.icon = icon + self.iconView = iconView + self.title = title + self.subtitle = subtitle + self.subtitleLineLimit = subtitleLineLimit + self.dashBalance = dashBalance + self.fiat = fiat + self.trailingView = trailingView + self.showsBalance = showsBalance + } +} diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift new file mode 100644 index 0000000..a2fd207 --- /dev/null +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift @@ -0,0 +1,63 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - ConverterRowSlot + +/// Identifies the two cards in the `ConverterCard` stack so the parent can position the arrow +/// badge on the seam between them. +enum ConverterRowSlot { + case top + case bottom +} + +// MARK: - ConverterRowHeightKey + +/// Reports each row's measured height up to `ConverterCard`, which uses the values to center the +/// arrow badge exactly between the two rows. Kept here next to the row that writes it. +struct ConverterRowHeightKey: PreferenceKey { + static let defaultValue: [ConverterRowSlot: CGFloat] = [:] + + static func reduce(value: inout [ConverterRowSlot: CGFloat], nextValue: () -> [ConverterRowSlot: CGFloat]) { + value.merge(nextValue()) { _, new in new } + } +} + +// MARK: - ConverterCardRow + +/// One card in the conversion stack: wraps arbitrary content with the shared, non-interactive +/// card chrome and reports its height via `ConverterRowHeightKey` for its `slot`. +@available(iOS 14, macOS 11, *) +struct ConverterCardRow: View { + let slot: ConverterRowSlot + @ViewBuilder var content: () -> Content + + var body: some View { + content() + .allowsHitTesting(false) + .padding(6) + .background(Color.dash.secondaryBackground) + .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) + .background( + GeometryReader { proxy in + Color.clear + .preference(key: ConverterRowHeightKey.self, value: [slot: proxy.size.height]) + } + ) + } +} diff --git a/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift b/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift new file mode 100644 index 0000000..beade79 --- /dev/null +++ b/Sources/DashUIKit/Components/EnterAmount/CurrencyOption.swift @@ -0,0 +1,70 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +// MARK: - DashCurrencySymbol (internal) + +enum DashCurrencySymbol { + /// Locale-based currency symbol (deterministic; no app state). + static func symbol(for currencyCode: String) -> String { + let locale = Locale.current as NSLocale + if let s = locale.displayName(forKey: .currencySymbol, value: currencyCode), !s.isEmpty { + return s + } + let f = NumberFormatter() + f.numberStyle = .currency + f.currencyCode = currencyCode + return f.currencySymbol + } +} + +// MARK: - CurrencyOption + +@available(iOS 14, macOS 11, *) +public enum CurrencyOption: Hashable { + case fiat(String) + case dash + case coin(String) + + public var isFiat: Bool { + if case .fiat = self { return true } + return false + } + + public var isCoinInput: Bool { + if case .coin = self { return true } + return false + } + + public var displayName: String { + switch self { + case .fiat(let code): return code + case .dash: return "DASH" + case .coin(let code): return code + } + } + + public var symbol: String? { + switch self { + case .fiat(let code): + return DashCurrencySymbol.symbol(for: code) + case .dash: return nil + case .coin(let code): return code + } + } +} diff --git a/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift b/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift new file mode 100644 index 0000000..b8243ec --- /dev/null +++ b/Sources/DashUIKit/Components/EnterAmount/DashBalanceView.swift @@ -0,0 +1,76 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - DashBalanceView + +/// Trailing balance for the convert source row: a symbol-free Dash amount followed by the Dash +/// symbol, with the fiat value beneath. Both strings come from the view model +/// (`dashBalanceFormatted` / `dashBalanceFiat`); pass `fiat: nil` to hide the fiat line. +@available(iOS 14, macOS 11, *) +public struct DashBalanceView: View { + /// Symbol-free formatted balance, e.g. "1.5". + public let balance: String + /// Formatted fiat value; `nil` hides the line (e.g. zero balance). + public var fiat: String? + + public init(balance: String, fiat: String? = nil) { + self.balance = balance + self.fiat = fiat + } + + public var body: some View { + VStack(alignment: .trailing, spacing: 0) { + HStack(spacing: 6) { + Text(balance) + .font(Font.dash.subhead) + .lineLimit(1) + + dashSymbol + } + .foregroundColor(Color.dash.primaryText) + + if let fiat { + Text(fiat) + .font(Font.dash.caption1) + .foregroundColor(Color.dash.primaryText) + } + } + } + + private var dashSymbol: some View { + Image(dash: .custom("icon_dash_currency", bundle: .dashUIKit)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12, height: 10) + } +} + +#if DEBUG + +@available(iOS 17, macOS 14, *) +#Preview { + VStack(spacing: 16) { + DashBalanceView(balance: "1.5", fiat: "$ 150.00") + DashBalanceView(balance: "0", fiat: nil) + } + .padding() + .background(Color.dash.secondaryBackground) +} + +#endif diff --git a/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift b/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift new file mode 100644 index 0000000..e9817ae --- /dev/null +++ b/Sources/DashUIKit/Components/EnterAmount/DashPickerView.swift @@ -0,0 +1,77 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@available(iOS 14, macOS 11, *) +private enum Layout { + static let hPadding: CGFloat = 6 + static let vPadding: CGFloat = 3 + static let cornerRadius: CGFloat = 6 +} + +@available(iOS 14, macOS 11, *) +public struct DashPickerView: View { + + public let options: [Option] + public let title: (Option) -> String + @Binding public var selected: Option + + public init(options: [Option], title: @escaping (Option) -> String, selected: Binding