mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-14 09:14:05 +01:00
New design (#2797)
* refactor node form UI to use new ListItem model * add new list item row types and widgets * add style wrapper * remove print [skip ci] * Integrate new design mockup with ViewModel (#2653) * feat: add Lightning Network support for Bitcoin wallets * refactor: rename `fiatConvertationStore` to `fiatConversionStore` for consistency and update related occurrences across codebase * feat: enhance address validation with Lightning Network invoice support for BTC & refactor wallet type/token checks in view model * feat: add support for Lightning invoice detection, refactor MWEB deposit/withdraw actions, and integrate Lightning transaction creation with updated priority handling * feat: add method to retrieve unused Spark deposit address for Bitcoin wallets * feat: add Breez API key support and update secrets handling for Bitcoin Lightning wallet integration in workflows * chore: update Breez SDK dependency to version 0.3.4 in pubspec files * Add bitcoin secrets config [skip ci] * feat: extend Lightning wallet functionality with transaction history fetching * feat: add LNURL-pay address detection and support in address parsing flow for Bitcoin Lightning integration * refactor: simplify `ReceivePageOption` logic * refactor: centralize `PaymentURI` generation logic across wallet types * feat: enhance `PaymentURI` handling with asynchronous support and Lightning-specific functionality * refactor: streamline `PaymentURI` logic and remove redundant URI implementations across wallet types * refactor: remove redundant debug print statement from `bitcoin_wallet_addresses.dart` * refactor: improve consistency in widget styling and centralized label logic, add Bitcoin Lightning deposit/withdraw support * feat: reload balance and tx history after sending a lightning transaction * feat: improve address formatting for human-readable addresses and update the default LNURL domain * fix: merge conflicts * feat: add error handling for LightningWallet initialization and adjust transaction direction logic * integrate homepage from new ui mockup * fix import * feat: add Lightning Network support for Bitcoin wallets * refactor: rename `fiatConvertationStore` to `fiatConversionStore` for consistency and update related occurrences across codebase * feat: enhance address validation with Lightning Network invoice support for BTC & refactor wallet type/token checks in view model * feat: add support for Lightning invoice detection, refactor MWEB deposit/withdraw actions, and integrate Lightning transaction creation with updated priority handling * feat: add method to retrieve unused Spark deposit address for Bitcoin wallets * feat: add Breez API key support and update secrets handling for Bitcoin Lightning wallet integration in workflows * chore: update Breez SDK dependency to version 0.3.4 in pubspec files * Add bitcoin secrets config [skip ci] * feat: extend Lightning wallet functionality with transaction history fetching * feat: add LNURL-pay address detection and support in address parsing flow for Bitcoin Lightning integration * refactor: simplify `ReceivePageOption` logic * refactor: centralize `PaymentURI` generation logic across wallet types * feat: enhance `PaymentURI` handling with asynchronous support and Lightning-specific functionality * refactor: streamline `PaymentURI` logic and remove redundant URI implementations across wallet types * refactor: remove redundant debug print statement from `bitcoin_wallet_addresses.dart` * refactor: improve consistency in widget styling and centralized label logic, add Bitcoin Lightning deposit/withdraw support * feat: reload balance and tx history after sending a lightning transaction * feat: improve address formatting for human-readable addresses and update the default LNURL domain * fix: merge conflicts * feat: add error handling for LightningWallet initialization and adjust transaction direction logic * integrate homepage from new ui mockup * fix import * add new-ui dir to pubspec_base * minor layout fixes * cleanup navbar logic * Modify navbar behaviour * smooth color change when selecting navbar options * open old ui pages with new ui action buttons * feat: working navbar in new ui * fix: minor sizing tweaks * CW-1266-integrate-bitcoin-lightning-through-spark-sdk (#2623) * feat: add Lightning Network support for Bitcoin wallets * refactor: rename `fiatConvertationStore` to `fiatConversionStore` for consistency and update related occurrences across codebase * feat: enhance address validation with Lightning Network invoice support for BTC & refactor wallet type/token checks in view model * feat: add support for Lightning invoice detection, refactor MWEB deposit/withdraw actions, and integrate Lightning transaction creation with updated priority handling * feat: add method to retrieve unused Spark deposit address for Bitcoin wallets * feat: add Breez API key support and update secrets handling for Bitcoin Lightning wallet integration in workflows * chore: update Breez SDK dependency to version 0.3.4 in pubspec files * Add bitcoin secrets config [skip ci] * feat: extend Lightning wallet functionality with transaction history fetching * feat: add LNURL-pay address detection and support in address parsing flow for Bitcoin Lightning integration * refactor: simplify `ReceivePageOption` logic * refactor: centralize `PaymentURI` generation logic across wallet types * feat: enhance `PaymentURI` handling with asynchronous support and Lightning-specific functionality * refactor: streamline `PaymentURI` logic and remove redundant URI implementations across wallet types * refactor: remove redundant debug print statement from `bitcoin_wallet_addresses.dart` * refactor: improve consistency in widget styling and centralized label logic, add Bitcoin Lightning deposit/withdraw support * feat: reload balance and tx history after sending a lightning transaction * feat: improve address formatting for human-readable addresses and update the default LNURL domain * fix: merge conflicts * feat: add error handling for LightningWallet initialization and adjust transaction direction logic * feat: enable private transactions by default in LightningWallet and update Breez SDK version to 0.4.2 * minor fixes [skip ci] * chore: fix some minor issues in comments (#2654) Signed-off-by: black5box <black5box@outlook.com> * fix: handle send-all functionality for LightningWallet transactions and adjust amount calculation logic * fix-german (#2659) * chore: update German localization strings for consistency and accuracy * chore: update German localization strings for consistency and accuracy [skip-ci] * feat: add LNURL support for address validation and LightningWallet compatibility, enhance error handling for OpenCryptoPay * fix: adjust LightningWallet amount parsing from 9 to 8 decimal places * feat: add LNURL support in LightningPaymentRequest and LightningWallet * Fix navigation gradient (#2657) * Fix navigation gradient * Fix CONFIG_ARGS formatting in app_config.sh (#2660) * fix: block wrongly parsed addresses (#2656) * fix: block wrongly parsed addresses * fix: move parsed address check into handlePaymentFlow method * fix: Handle QR URLs separately for pay anything flow * feat: add electrum seed support for Lightning * refactor: improve `parseFixed` logic and add comprehensive unit tests for edge cases (#2661) * Update cw_bitcoin/lib/bitcoin_wallet.dart [skip ci] * resolve conflict issue --------- Signed-off-by: black5box <black5box@outlook.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> Co-authored-by: black5box <black5box@outlook.com> Co-authored-by: tuxsudo <tuxsudo@tux.pizza> Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com> * remove old code [skip ci] * fix: page widgets rebuilt every time * chore: formatting * remove unused navbar * fix: lightning balance on balance card in lightning mode * fix: return if uri == null * feat: trade history in new ui * feat: enhance balance model with secondary asset handling * feat: unique card colors and icons for coins * wip: card customization * fix: merge conflict * feat: customizable balance card * Fix some spacings and test sizing * Fix formatting * feat: add Lightning Network support for send flow and fee handling adjustments * refactor: remove unnecessary debug print statement in send flow for Lightning Network balance * feat: integrate OpenCryptoPay QR handling in send flow and improve scan action handling logic * feat: add maxDepositClaimFee parameter for Lightning wallet configuration * Switch balance card to RoundedSuperellipse and set color list to Wrap * Fix * Switch to scroll view * refactor: update navigation route from `receive` to `addressPage` in coin action row * feat: bloc-based viewmodel for card customizer * feat: mobx-independent balance card customizer vm * minor fixes * copyWith constructor for cleaner state changes * refactor: wrap `HistorySection` with `Observer` for reactive state updates, update navigation for "Buy" action, and fix incorrect balance property * feat: enhance Bitcoin receive flow with Lightning and SegWit options, update address page registration, and localize coin action labels * feat: add helper methods for Lightning and SegWit receive page options in Bitcoin configuration * feat: add Lightning transaction type handling and enable navigation to transaction details from history * Add more special card types * feat: new ui settings page * feat: add Lightning actions to balance cards, refactor card rendering logic * refactor: update settings page localization, hide unused settings items * feat: add secrets generation for cw_bitcoin, including breezApiKey * Lightning switcher animation (#2725) * Properly animate bitcoin/lightning switcher * Cleanup and fix sizing * Final cleanup * Fix action button colors (#2732) * feat: add hardware wallet type handling and improve parsed address reset flow * Refactor settings page to use generic row items (#2745) * fix android back button in settings on android, force iphone-style transitions on all platforms * move settings page to generic row impl * fix imports * Balance card fixes (#2729) * Update balance card sizing * Fix spacing * Initial font changes (#2731) * Initial font changes * More font changes * feat: integrate Lightning transaction support and improve transaction history UX - Enhanced dashboard to compute confirmations using the first and last transactions. - Updated `BitcoinWallet` to subscribe for and manage Lightning transactions. - Improved `HistoryTile` layout and introduced new leading icons for Lightning transactions. * feat: add unclaimed deposit actions and enhance Lightning event handling * release changes [skip ci] * Enhanced card customizer (#2744) * enhanced card customizer * Fix conflicts * Add missing icons + adjust spacing --------- Co-authored-by: tuxsudo <tuxsudo@tux.pizza> * feat: enhance Lightning invoice handling and address validation - Added support for identifying non-zero amount Lightning invoices. - Refined invoice matching with updated regex patterns. - Improved UI to conditionally display addresses in sending confirmations. - Adjusted logic to handle zero-value Lightning invoices. * feat: add `isPayjoinAvailable` computed property and update Payjoin visibility logic * feat: improve Lightning wallet handling and block explorer logic * update patch version [skip ci] --------- Signed-off-by: black5box <black5box@outlook.com> Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> Co-authored-by: tuxsudo <tuxsudo@tux.pizza> Co-authored-by: black5box <black5box@outlook.com> Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com> * minor new ui fixes * padding for new row separators * remove debug prints * New design improve nav bar (#2763) * tweak navigation bar touch targets and sizing * feat: add tests for identifying zero and non-zero amount Lightning invoices (#2772) * feat: add tests for identifying zero and non-zero amount Lightning invoices - Added unit tests to validate recognition of Bolt11 zero-amount and non-zero-amount invoices. - Enhanced `isBolt11ZeroInvoice` function to handle `lightning:` prefixed invoices. * fix: correct currency selection logic for Lightning wallets in Send ViewModel * feat: improve Lightning wallet initialization and address generation - Updated `init` method in `LightningWallet` to return a boolean for initialization success, adding error handling for failed setups. - Integrated dynamic name generation using `generateName` for Lightning wallet addresses. * refactor: relocate `generate_name` to `cw_core` and update imports * chore: update breez sdk (#2789) * Wire tokens and NFTs to new Home page (#2804) * wiring for assets on home page * fix imports * wire nft page in new ui * fix nft display in new ui * fix totalWidth calc * add tokens button + bugfixes * add tokens button + bugfixes * fix late initialization * fix history section exception * fixes * Add haptic feedback (#2813) * Add haptic feedback * Add haptic feedback to correct ActionButton * Return bottom gradient (#2808) * Add IgnorePointer to bottom nav gradient (#2822) * New design send page (#2791) * new ui send page (wip) * coin control page improvements * fee settings, new send confirmation modal * minor aesthetic fixes * load network name without cw_bitcoin import * recipient dot cleanup * minor logic fixes * ui polish * - add aliaspay support - fix wording for max button - fix back button action in modals * Sizing changes * dismiss transaction on modal close * sizing fix * - fix remove recipient icon - properly round amounts - fix coin control page spacing * - fix remove recipient icon - properly round amounts - fix coin control page spacing * - fix button sizing - add description for fees page * Update lib/view_model/unspent_coins/unspent_coins_list_view_model.dart [skip ci] * Update lib/view_model/send/send_view_model.dart [skip ci] --------- Co-authored-by: tuxsudo <tuxsudo@tux.pizza> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Fix conflicts with dev * Fix conflicts with dev * Add zcash receive options to new design flow * wire in missing settings in new ui * up migration version * formatting * translations * lint.sh * New design receive page (#2834) * feat: viewmodel on new receive page (wip) * feat: address type selector for new ui receive page * fix: minor fixes to touch targets and tap anims * minor ui fix * wip receive page changes * receive page + address selection page * receive page + address selection page * add font * add svg * minor ui fixes * receive page fixes/improvements * change receive page modal design * further ui polish * icons for infobox * fix restricted import * fix closing label modal * - fix balance card appearance on address page - fix search bar position - fix mweb position in address type selector * receive page fixes * fix anonpay * remove restricted import * fixes * fix receive on zec * simplify chain id * lint fix * fix migration numbering Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fix migration version Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fix settings * readability * fix formatting * move dismiss infobox to vm * disable addr rotation on zcash * remove _setEffects * translations --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * New UI Sync bar (#2831) * syncbar * small styling fixes * modify duration * lint.sh * syncHeavy wallet types Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * update translations, add pull-down refresh, separate top bar components to files --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * alphabetize * New design balance card icons (#2824) * Add the other crypto card icons * Update Zano, add Zcash and Arbitrum * Update default card style and available gradients * Fix broken preview * Fix animation * Color combinations fix * Minor updates * Add decred icon * if(!mounted) return; * New design minor fixes (#2842) * More minor fixes * Update modal sheet behaviour * Update missing currency image links * List row and navigation bar tweaks * Minor navbar fix * New UI Swap page (#2829) * new ui send page (wip) * coin control page improvements * fee settings, new send confirmation modal * minor aesthetic fixes * load network name without cw_bitcoin import * recipient dot cleanup * minor logic fixes * ui polish * - add aliaspay support - fix wording for max button - fix back button action in modals * Sizing changes * dismiss transaction on modal close * sizing fix * - fix remove recipient icon - properly round amounts - fix coin control page spacing * - fix remove recipient icon - properly round amounts - fix coin control page spacing * - fix button sizing - add description for fees page * swap page * fixes * further fixes * merge * png -> svg * minor condition changes * lint.sh * fix swapsxyz isCentralized Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * translations, readability improvements, remove debug rows * cleanup * alphabetize strings * small bugfixes --------- Co-authored-by: tuxsudo <tuxsudo@tux.pizza> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fix new ui coin control page layout (#2843) * New design lightning flows (#2821) * new ui send page (wip) * coin control page improvements * fee settings, new send confirmation modal * minor aesthetic fixes * load network name without cw_bitcoin import * recipient dot cleanup * minor logic fixes * ui polish * - add aliaspay support - fix wording for max button - fix back button action in modals * Sizing changes * dismiss transaction on modal close * sizing fix * - fix remove recipient icon - properly round amounts - fix coin control page spacing * - fix remove recipient icon - properly round amounts - fix coin control page spacing * - fix button sizing - add description for fees page * lightning flow wip * lightning flow wip #2 * lightning withdraw flow * lightning deposit flow (wip, currently works but really janky) * send from external + bugfixes * merge fixes * merge fixes * fixes * Update lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart --------- Co-authored-by: tuxsudo <tuxsudo@tux.pizza> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fix covered create wallet button * fix: close modal after transaction commitment in Send ViewModel (#2858) * New design tweaks (#2847) * Add more haptic feedback * Fix background gradients and modify SafeArea in home_page.dart * Fix history padding, fix divider color, disable Charts, update base_page.dart appbar style, fix nav bar border + height on iOS * Lightning Username setup flow for new UI (#2845) * lightning username edit/create flow * fix button color * fix layout * fixes * remove character limitation for internal builds * remove dependency on cw_bitcoin * fix username requirements * fixes * condition fix * Update lib/new-ui/pages/lightning_username_page.dart --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> * keyboard hide overlay for iphone * New design UI tweaks (#2870) * Fix nav height on Android * Fix blue gradient * Update transactions view and minor UI tweaks * CW1011-Allow-users-to-view-bitcoin-amounts-in-sats (#2678) * feat: introduce `formatFixed` integration and sats display preference * feat: enable display of Bitcoin amounts in satoshis and refactor amount formatting logic * refactor: consolidate fee formatting logic and add support for MWEB availability check * feat: add support for satoshi-based amount display and enhance fiat conversion logic * feat: add support for satoshi-based amount parsing, formatting, and display preferences across buy/sell flow and UI adjustments * feat: implement `AmountParsingProxy` with unit tests for parsing and formatting crypto amounts in various display modes * feat: replace `preferBalanceInSats` with `displayAmountsInSatoshi` and introduce `AmountParsingProxy` for unified crypto amount handling across the app * refactor: streamline amount formatting logic and enhance unspent coins handling with `AmountParsingProxy` integration for unified crypto processing * refactor: replace direct bitcoin amount formatting with `AmountParsingProxy` and remove debug print in `exchange_view_model` * refactor: replace `getIt<AmountParsingProxy>` with `_appStore.amountParsingProxy`, remove redundant dependencies across view models and adjust related imports * refactor: remove redundant `AmountParsingProxy` registration in DI setup to simplify dependency management [skip ci] * feat: add getCryptoSymbol to simplify the Symbol selection between Sats and Bitcoin * fix: merge conflict * Revert "fix: merge conflict" This reverts commit 6debdaa0466747d6fcc85d6bd0be115e6fb4d856. * fix import [skip ci] * feat: remove number formatter from exchangeVM * refactor: replace `SettingsStore` with `AppStore` across view models, integrate `AmountParsingProxy` for amount formatting, and clean up redundant imports * refactor: remove debug print statements from `getCryptoOutputAmount` in `AmountParsingProxy` * feat: enhance fee and deposit amount formatting with crypto symbols and improved parsing * feat: integrate `AmountParsingProxy` for improved Bitcoin amount parsing in `PaymentURI` and fiat-to-crypto conversion * feat: append crypto symbols to Bitcoin transaction amounts in dashboard view * feat: improve crypto amount parsing in SendCard widget and Output view model using `AmountParsingProxy` * feat: conditionally show Bitcoin amount display setting for Bitcoin wallets and refactor `DisplaySettingsViewModel` to use `AppStore` * refactor: clean up redundant imports in `bitcoin_wallet.dart` and `electrum_wallet.dart` * feat: append crypto symbols to fee values in send and cake pay card widgets for improved clarity * refactor: replace `currencyTitle` with `currency` across widgets and integrate `AmountParsingProxy` for improved crypto amount formatting and parsing * refactor: improve validation logic and amount parsing in SendCard and Output widgets * refactor: standardize amount parsing and formatting logic with `AmountParsingProxy` across ViewModels and widgets * refactor: `AppStore` remove debug prints * refactor: rename amount parsing/formatting methods for better clarity in tests [skip ci] * fix: AmountParsingProxy and optimize `cryptoAmount` handling in BuySellViewModel * fix: adjust balance calculation for Monero and Wownero to exclude unlocked amount from full balance * fix: correct `_appStore` usage and format adjustments in `SendViewModel` * feat: enhance satoshi on new-ui * feat: add computed property for `amountParsingProxy` in AppStore * feat: localize Bitcoin amount display option and update asset history UI * feat: improve exchange amount processing and refactor fiat conversion values --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> * New design unconfirmed balance widget (#2861) * unconfirmed balance display widget for new ui * unconfirmed balance display widget for new ui * merge * alphabetize translations * New UI fixes (#2860) * fix asset top bar * improve wordmark in large qr mode on receive * receive option fixes * exchange -> swap * send page fixes * update pubspec base * go to home on wallet change * nicer card customizer * don't show account name for single-account wallets * special card design for lightning * fiat amount bar improvement * update translations * add sending indicator * fix non-number input to receive amount * de-jank fiat input * nicer balance card animation when switching btc-ln * nicer balance card animation when switching btc-ln * fix backup password input * display time+date in history * fix error on buy * fix incorrect wallet showing in wallets tab * fix wallet list layout * only show coin control option on hasCoinControl * better error on send page * fiat value in coin control * add zano to isSyncHeeavy * fix keys in settings * fix assets condition * fix import * remove BasePage dependency in RescanPage * add missing settings * show send/receive in swap confirm sheet * fix receive background on ios * fix assets spacing * fix trade tile sizing * fix padding * fix icon in wallet select modal * change text on swap initial modal * spacing * l2 send external modal styling * fix null on swap confirm sheet * change infobox text * add chain icon to asset widgets * add chain icon to asset widgets * disable swap page reset * fix wallet list page layout * revert merge conflict issue [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Account Customizer for new UI Monero (#2859) * new ui monero account management (wip) * account reordering wip * account reordering wip * account reordering (works) * cards view compact mode * format * translation * name generation * fix for non-xmr wallets * } * allow reset order, fallback on improperly saved order * translation * fix for pre-reorder wallet loading * fix loading * - save cards on modal slide-down - always load active account as first * fix card customizer close anim * fixes * fix condition * merge * fix migration versions * use updated flutter rust bridge in lightning and payjoin * mvp fixes * new ui mweb mask/unmask * fallback for balance card amount format * New UI tweaks (#2872) * Update lightning transaction history view * Add BNB card design * Color, padding, and radius fixes * Fix wallet list bottom section & gradient * Update Apps page * reformat * nicer sat picker * fix keyboard * chain badges * enhance styling for card customizer * mweb settings icon * mweb chain badge * update wording in settings * bnb chain icon * always round send amount * always round all amount * token placeholder * Fix ModernButton colors (#2888) * Fix ModernButton colors * Minor * guard for unconfirmed balance widget * fix error on wallet switch * New design mvp fixes (#2890) * mvp fixes * fallback for balance card amount format * reformat * nicer sat picker * fix keyboard * chain badges * enhance styling for card customizer * bnb chain icon * always round send amount * always round all amount * guard for unconfirmed balance widget * fix error on wallet switch * Update lib/new-ui/pages/account_customizer.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fixes --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Install Protoc in reusable build workflow Added step to install Protoc before building. * Fix protobuf-compiler installation step Update package list before installing protobuf-compiler. * Modify Protoc installation command to handle update errors Change the install command for Protoc to avoid failure on update. * Streamline Lightning wallet balance management and crypto handling (#2893) * refactor: streamline crypto amount handling and improve Lightning balance management - Removed redundancy in balance calculations for Lightning wallets. - Centralized crypto symbol creation with `selectedCryptoCurrencySymbol`. - Enhanced crypto amount handling and formatting with `AmountParsingProxy`. - Simplified logic for displaying Lightning balances across UI components. * refactor: remove sats display logic and unused formatting from BalanceCard widget * chore: update breez-sdk-spark-flutter to latest commit * feat: enhance crypto amount formatting with localized separators and max decimals * fixes * fix: handle null balance records in Lightning wallet cards * New design add validators (#2894) * add validation, fix node form * Add validator to send amount input field Added a validator to the amount input field. --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Disable 'addresses' button on receive screen for BTC LN (#2896) * feat: Disable addresses button on receive screen for BTC LN * chore: remove debug print in uri computation * feat: introduce swap action toggle and UI updates * feat: conditionally display Zcash card setting for Zcash wallets in Display Settings * Fix leading button color (#2898) * fix amount format * fix loadCards * pre-beta fixes * cyjan: fix: order being null for certain index * pre-beta fixes v2 (#2905) * minor context check [skip ci] * persist fee priority for zcash (#2907) * Add support for Zcash address detection and amount logic refactor (#2906) * refactor: consolidate crypto amount handling with `displayAmount` and `cryptoCurrencySymbol`, improve fiat conversion logic, and optimize token currency management * refactor: simplify `displayAmount` logic and remove unused `_rawAmount` property * feat: add support for Zcash transparent address detection and simplify amount conversion logic * Improve Lightning invoice handling and crypto formatting (#2908) * fix: update Lightning address regex to support additional formats and prefixes * fix: conditionally render advanced settings dropdown based on fees or coin control availability * feat: enhance address detection, Lightning invoice handling, and crypto formatting * feat: improve Lightning invoice parsing and crypto amount formatting on send page * chore: remove unnecessary exception printing in `getBolt11Amount` function * Fix lightning swap * import fix (hide Mac;) * parse -> tryParse * Transaction Commited -> Transaction Sent! * feat: enable clipboard paste for deposit and receive addresses in swap page (#2909) * fix: resolving LNURLS (#2911) * new UI pre-beta fixes (#2910) * fixes * parse -> tryParse * change sat display settings style * minor fix until hive is removed [skip ci] * Remove redundant logic and improve send page features (#2913) * fix: update crypto amount parsing logic in send confirmation sheet * feat: Added checkbox to toggle Litecoin MWEB coins on the send page. fix: LNUrlPayRecord * fix: remove redundant `.withLocalSeperator()` from fiat balance formatting logic * fix: update Bitcoin amount display titles to align with consistent formatting standards * force boolean to update (#2916) * force boolean to update * fix bnb balance * fixes #4 (#2917) * only display sats for btc wallets in card customizer * fix: resolve async handling of Lightning transaction history in `fetchTransactions` method (#2919) * New design pre beta fixes 5 (#2918) * improve l2 deposit/withdraw flow layouts * add copied indicator * SafeArea * fix addr rotation * fix styling of lightning switcher * no raster graphics in new ui please * optimization for assets/history section * remove unused bloc provider * optimize dashboard + fix duplicated name * disable lightning mode on wallet change * remove broken animation * safearea around nodes page * fix rescan page layout * fix exception on really fast switching * remove broken blur * card customizer autosave * remove debug print * add better fallbacks for currency titles * revert due to broken animation * fix send page crash * fix chain title for arb * fix cupertino date picker * Update lib/new-ui/widgets/receive_page/receive_bottom_buttons.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * feat: add focus management and external address validation to send page forms - Introduced `focusNode` to `NewSendAddressInput` for better input handling. - Enhanced address validation by synchronizing with parsed external addresses. - Updated logic for non-MWEB unspent coin type in send actions. - Suppressed unnecessary exception logs in Bitcoin wallet transactions. * New design swap page fixes (#2923) * swap page fixes * add scroll controller * comment * tweak refresh pulldown (#2924) * add copy button to l2 send external modal (#2925) * account customizer padding * feat: lightning transaction fetching and Lightning wallet integration (#2927) - Allow nullable `height` in `ElectrumTransactionInfo`. - Add `fromDate` support for filtered Lightning transaction history retrieval. - Simplify Lightning address handling logic in wallet addresses. - Optimize Lightning wallet transaction syncing based on the last Lightning transaction date. - Clean up imports in `electrum_transaction_history.dart`. * New balance handeling (#2931) * feat: lightning transaction fetching and Lightning wallet integration - Allow nullable `height` in `ElectrumTransactionInfo`. - Add `fromDate` support for filtered Lightning transaction history retrieval. - Simplify Lightning address handling logic in wallet addresses. - Optimize Lightning wallet transaction syncing based on the last Lightning transaction date. - Clean up imports in `electrum_transaction_history.dart`. * feat: add support for Lightning balance in wallet snapshot and initialization * Disable Lightning switcher for hardware wallets (#2932) * fix: disable Lightning switcher for hardware wallets * fix: update wallet type check to use `isSoftwareWallet` for Lightning support * New design pre beta fixes 6 (#2929) * fix swaps from ln * prevent crash if ln balance isn't loaded yet * fix currency icons disappearing on swap confirm sheet * fix swap fiat validation * display ln at top on swap currency selector in btc wallets * update icon for card options * display default fiat and crypto at top of currency picker * fix fiat amount bar formatting * add modal scroll controller to provider logs * revert premature optimization (sizing issue) * New design pre beta fixes 7 (#2935) * fix swaps from ln * prevent crash if ln balance isn't loaded yet * fix currency icons disappearing on swap confirm sheet * fix swap fiat validation * display ln at top on swap currency selector in btc wallets * update icon for card options * display default fiat and crypto at top of currency picker * fix fiat amount bar formatting * add modal scroll controller to provider logs * revert premature optimization (sizing issue) * copyable trade id * disable swaptrade on ln * set isFixedRate properly * update color * display trocador provider on swap confirmation sheet * add "exchange rate is fixed" text --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * updated l2 modal (#2934) * display names for send addr input (#2936) * fix: MWEB toggle logic on send page (#2938) * feat: add retry logic to `getAddress` in Lightning wallet (#2939) * Enhance crypto address handling and add Lightning invoice support (#2937) * feat: enhance crypto address handling and add Lightning invoice support - Added `_overrideFromCryptoCurrency` and `_overrideToCryptoCurrency` for Zcash unified and shielded addresses. - Improved Lightning invoice generation with `getLightningInvoice` for Bitcoin wallets. - Updated error handling in exchange provider to accommodate API changes. - Introduced Lightning invoice fallback for deposit and receive addresses in ExchangeViewModel. * feat: Added `_overrideFromCryptoCurrency` and `_overrideToCryptoCurrency` for Zcash unified and shielded addresses. * fix pay anything on paste button (#2940) * Prevent multiple paste triggering any pay flow * fixup fiat input (#2941) * New design small fixes (#2920) * Fix optical symmetry for swipe to send text * Update nav bar icons * Top bar and card tweaks * Update paddings --------- Co-authored-by: malik1004x <malikowskirobert@gmail.com> * fix erc20 balance display (#2944) * fix erc20 balance display * route through cw_evm * New design beta fixes (#2943) * fix asset tile layout * fix card customizer keyboard jumping * fix cardsview exception * fix swap modal height * remove syncbar percentage * add copy icon to trade id * fix trade confirmation appearing twice * remove useless mweb setting * add tag to contact currency selector text * prettify picker for ln * fix receive crash * always route to NewSendPage * handle ln amount on paste * handle ln amount on payment request creation * prettify ln errors * readd "blocks" word * register deeplinks * Enhance bitcoin error message [skip ci] * fix support chat page (#2946) * new-design-fix-confirm-sheet-parsing-error (#2950) * feat: use bigint instead of doubles * feat: use bigint instead of doubles * feat: use bigint instead of doubles * fix: also show sats in confirm sheet * fix: also show sats in confirm sheet * feat: localize amounts and cap amount in tx history (#2951) * fix: linux CI * feat: localize token balances (#2954) * fix: send amount hww (#2955) * chore: use latest breez sdk (#2953) * fix addAddressWord * fixes (#2957) * receive fixes (#2959) * fix: stabilize android build by increasing jvm heap size (#2958) * fix: lighting by having the new SDK Save into a new location (#2961) * New design rc fixes (#2960) * additional safeguards for isSyncHeavy check * fix account name getting reset * update base icon * fix units in swap * readd memos to zec * New design minor UI (#2956) * Wrap text for list item widgets * Update lightning address and QR icons * Update near intents icon * Fix share issue * Fix (#2963) * fix tx amount formatting * rename currency * change wording for zec memo * fix base icon * fix app icon script [skip ci] * New design rc fixes 2 (#2965) * fix account name changes * fix spacing on addresses page * add payjoin copy modal * improve memo ui * potential fix for ci * Set default address for zcash and bitcoin for buy flow (#2979) * new monero.com icons (#2980) * parse and validate deposit amount from api (#2967) * generic fixes * minor context fix [skip ci] * Generic fixes (#2982) - solana send all bug - pasting SOL address triggering payanything flow - add more address types for payanything detection * rescan fix * about page (#2974) Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * update settings icons/wording (#2973) * Changelog modal (New UI) (#2971) * add new changelog modal * typo * print -> printV * Update lib/new-ui/widgets/changelog_modal.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fix node form (#2970) * new-design-add-breez-logging (#2984) * feat: add Lightning log export functionality for Bitcoin wallets * feat: add multilingual support for Lightning log export strings * feat: add Lightning support toggle in settings and translations for multiple languages * refactor: simplify `setUseLightning` by removing redundant store update * Update cw_bitcoin/lib/electrum_wallet.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * minor fixes [skip ci] * minor context fixe [skip ci] * fix: make walletinfoid unique to prevent duplicates (#2969) * fix: walletconnect not working in new design builds (#2993) * new-design-fix-mweb-coin-enabling (#2995) * feat: add unspent coin type handling to asset details modal send button * fix: improve German translations for wallet, seed, and Lightning-related strings * fix: update Dutch translations to correct terminology and formatting #2992 (#2997) * fix: drop gn (#3000) * vulnerable seeds popup on new ui (#3004) * update mac os monero.com icons (#3003) * Add Payjoin log export and improve logging behavior (#2999) * fix: suppress exception logging in `getAddress` method of Lightning wallet - Added a try-catch block to avoid unnecessary exception logs during Lightning address retries. * fix: update fallback message for missing Lightning address detection * feat: add Payjoin log export and logging enhancements * fix: localize "Export Payjoin Logs" string in settings page * fix: litecoin mweb balance being included in the second asset (#2998) * Minor fixes (#2994) * Minor fixes * Update lib/new-ui/new_dashboard.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * New design post release fixes (#3002) * fix: only throw on deserialize if kDebugMode is true (#2976) This closes #2972, in debug mode we will still catch these issues but if somehow that happens on production it will fallback to safe default * fix: update docs and dockerfile (#2975) * fix: missing steps for linux build [skip ci] (#2985) * move balance to bigint * move balance to bigint * fix electrum issue * fix unconfirmed balance * fixes * Fix minor issue in Spanish translation (#2991) * Fix minor issue in French translation (#2987) * Fix minor issues in German translation (#2986) * update constructor calls in configure.dart * fixes * remove expnded * V6.0.1 Early Release Candidate --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Edwin den Boer <woordenboer@planet.nl> Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> * Swaps xyz swap enhancements (#2615) * refactor token cache and limit handling * Improve SwapsXYZ external send logic * Improve SwapsXYZ fixed-rate handling * disable fixedRate for SwapsXyz * SwapsXYZ contract call * Add track url for swaps xyz * add arbitrum support * add check for unsupported source tokens * enable SwapsXyzExchangeProvider in provider list * refactor SwapsXYZ provider and trade flow handling * Update exchange_trade_view_model.dart * validate evm call-data * Revert "validate evm call-data" * fix msg value for ERC-20 transfers * fix flag for swapsXYZ send check * Update send_view_model.dart * handle Swaps.xyz method selectors & approvals * fix evm approvals * Improve EVM token approval & receipt handling * merge split token transfers in evm tx * fix token allowance check for approvals * add sourceToken validation to EVM tx creation * prioritizing incoming transactions over fetched outgoing ones * compute net flow when merging transactions * add truncateTrailingText to list items and widgets --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> Co-authored-by: Serhii-Borodenko <17529954+Serhii-Borodenko@users.noreply.github.com> * fix wownero balance (#3007) * fix keyboard (#3006) * fix android build * check if stream is closed [skip ci] * [skip ci] lint * fix: populate amount field (#3009) * fix address rotation exception (#3011) * copy uri with amount from recieve page (#3010) * add "transaction sent!" to swap confirmation sheet (#3012) * fix wallet change on token swaps (#3013) * Fix fee estimation and recipient logic in Lightning Network (#3014) * fix: update `hasMultiRecipient` logic to include non-Lightning coin type check * fix: Lightning Network fee estimation and parsing logic * fix: adjust `sumByBigInt` logic to handle `sendAll` flag in send page * fix: move sqlite migrations to sqlite.dart (#3008) * fix: move sqlite migrations to sqlite.dart fix: backup restore not initializing SQL properly * Update cw_core/lib/db/sqlite.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * feat: update currency symbol handling and extend Electrum transaction details (#3016) * Use isCentralized for truncation trade id [skip ci] * fix: Incorrect token amount for tokens and filter out new spam transactions after swaps (#3015) * New design release swap fixes (#3018) * fix deserialize ln * fix get wallet type for bnb tokens * show extraId for swaps * chore: translate using gpt-5.2 (#3021) * add NEAR send-all validation * tiny ui fixes (#3023) * use double parsing for amount comparison[skip ci] * adjust lightning address error message * feat: add BTC LN support to available methods and payment data handling * feat: add cached Lightning address handling for improved performance (#3025) - Introduced `cachedAddress` field to store and reuse the last fetched Lightning address. - Updated `getLightningAddress` method to fallback on `cachedAddress` if no new address is fetched. - Extended wallet constructors and snapshots to include `cachedLightningAddress` for persistence. * Revert transaction history color * Minor * Apply suggestion from @malik1004x * new-design-post-release-fixes-3 (#3028) * fix unnecessary account switching * fix exception on bad payment amount * fallback for currencies on external swap modal * increase balance card touch target * don't use lightning addresses for exchange * increase max length for ln encoding * don't show infobox on ln receive * fix showing old tx details (null tx on createTransaction) * remove unused import * refactor: relocate `_logStream` declaration in `LightningWallet` for better code organization (#3029) * monero build fix update build numbers fix best rate null [skip ci] * hide swap on mweb asset * minor context fix [skip ci] * Fix Silent payment case * new-ui: fix changelog typo (#3036) Fix typo in changelog for account management. * Modify date format to Month DD YYYY * feat: docker based ci * Fix 2FA continue button location (#3043) * fix: Desktop wallet bugs (#2968) * fix: Desktop wallet bugs - repeated wallets post migration - newly created wallets not showing up - extra large components on desktop, needed maxwidth * chore: Remove duplicate logic * chore: remove formatting * refactor: streamline updateList method and handle multiple calls with a future based mutex * refactor: enhance swipe to confirm with dynamic width handling and improved drag threshold logic * Improve error handling and duplicate invoice handling in Lightning wallet (#3047) * fix: handle duplicate Lightning invoice payments gracefully * fix: improved error handling in Lightning wallet * General improvements (#3030) * General Improvements - Make borderRadius for CTAs uniform 18px - Identify hardware wallets in title of Seeds/Keys page - Make entire wallet tile clickable in wallet list * chore: Simplify check * Update lib/view_model/wallet_keys_view_model.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * homescreen improvements (#3034) * Homescreen improvements - Increase touch area for card editing icon - Tapping any card in the background should move it ino the foreground without closing the account tab - make top left chain icon primary color with full opacity when syncing and 20% when done - add tor indicator to top bar when enabled * fix: remove unneeded color filter * refactor: Adjust structure for tor and sync indicators * minor fixes (#3045) * Enhance LNURL payment integration (#3041) * feat: enhance LNURL support and Lightning payment handling - Improve LNURL decoding and encoding with expanded prefix handling. - Add support for parsing and validating LNURL payment invoices. - Introduce logic for fetching LNURL payment request amounts. - Update payment flow to integrate LNURL and Lightning-specific conditions. - Refactor BOLT11 prefix validation and amount extraction logic. * fix: simplify LNURL invoice validation by removing redundant BOLT11 check --------- Signed-off-by: black5box <black5box@outlook.com> Co-authored-by: Serhii <borodenko.sv@gmail.com> Co-authored-by: malik1004x <malikowskirobert@gmail.com> Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: tuxsudo <tuxsudo@tux.pizza> Co-authored-by: black5box <black5box@outlook.com> Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Co-authored-by: Konstantin Ullrich <konstantin@cakewallet.com> Co-authored-by: Edwin den Boer <woordenboer@planet.nl> Co-authored-by: Serhii <17529954+serhii-bor@users.noreply.github.com> Co-authored-by: Serhii-Borodenko <17529954+Serhii-Borodenko@users.noreply.github.com> Co-authored-by: rottenwheel <92872541+rottenwheel@users.noreply.github.com>
This commit is contained in:
co-authored by
Serhii
malik1004x
Konstantin Ullrich
tuxsudo
black5box
cyan
David Adegoke
Konstantin Ullrich
Edwin den Boer
Serhii
Serhii-Borodenko
rottenwheel
parent
18c36acc14
commit
084fd6bb1e
+108
-34
@@ -153,16 +153,17 @@ class CWBitcoin extends Bitcoin {
|
||||
return BitcoinTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo,
|
||||
extra: out.extra))
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo.isNotEmpty ? out.memo : null,
|
||||
extra: out.extra,
|
||||
))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: bitcoinFeeRate,
|
||||
@@ -264,6 +265,7 @@ class CWBitcoin extends Bitcoin {
|
||||
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
||||
case UnspentCoinType.nonMweb:
|
||||
return element.bitcoinAddressRecord.type != SegwitAddresType.mweb;
|
||||
case UnspentCoinType.lightning:
|
||||
case UnspentCoinType.any:
|
||||
return true;
|
||||
}
|
||||
@@ -319,30 +321,6 @@ class CWBitcoin extends Bitcoin {
|
||||
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getBitcoinReceivePageOptions(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
final keys = bitcoinWallet.keys;
|
||||
if (keys.privateKey.isEmpty) {
|
||||
return BitcoinReceivePageOption.allViewOnly;
|
||||
}
|
||||
return BitcoinReceivePageOption.all;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet) {
|
||||
final litecoinWallet = wallet as ElectrumWallet;
|
||||
if (Platform.isLinux ||
|
||||
Platform.isMacOS ||
|
||||
Platform.isWindows ||
|
||||
litecoinWallet.isHardwareWallet) {
|
||||
return BitcoinReceivePageOption.allLitecoin
|
||||
.where((element) => element != BitcoinReceivePageOption.mweb)
|
||||
.toList();
|
||||
}
|
||||
return BitcoinReceivePageOption.allLitecoin;
|
||||
}
|
||||
|
||||
@override
|
||||
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) {
|
||||
switch (option) {
|
||||
@@ -362,6 +340,13 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
BitcoinReceivePageOption getBitcoinLightningReceivePageOption() => BitcoinReceivePageOption.lightning;
|
||||
@override
|
||||
BitcoinReceivePageOption getBitcoinSegwitPageOption() => BitcoinReceivePageOption.p2wpkh;
|
||||
@override
|
||||
BitcoinReceivePageOption getLitecoinMwebReceivePageOption() => BitcoinReceivePageOption.mweb;
|
||||
|
||||
@override
|
||||
Future<List<DerivationType>> compareDerivationMethods(
|
||||
{required String mnemonic, required Node node}) async {
|
||||
@@ -703,6 +688,8 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs) {
|
||||
if (pendingTransaction is PendingLightningTransaction) return outputs;
|
||||
|
||||
final pendingTx = pendingTransaction as PendingBitcoinTransaction;
|
||||
|
||||
if (!pendingTx.hasSilentPayment) {
|
||||
@@ -780,6 +767,15 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getUnusedSpakDepositAddress(Object wallet) async {
|
||||
try {
|
||||
final bitcoinWallet = wallet as BitcoinWallet;
|
||||
return wallet.lightningWallet?.getDepositAddress();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> commitPsbtUR(Object wallet, List<String> urCodes) {
|
||||
if (wallet is LitecoinWallet) return wallet.commitPsbtUR(urCodes);
|
||||
@@ -804,6 +800,22 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool useLightning(Object wallet) {
|
||||
final _wallet = wallet as ElectrumWallet;
|
||||
if (_wallet is BitcoinWallet) return _wallet.useLightning;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void updateUseLightning(Object wallet, bool value) {
|
||||
final _wallet = wallet as ElectrumWallet;
|
||||
if (_wallet is BitcoinWallet) {
|
||||
_wallet.useLightning = value;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void resumePayjoinSessions(Object wallet) {
|
||||
final _wallet = wallet as ElectrumWallet;
|
||||
@@ -839,4 +851,66 @@ class CWBitcoin extends Bitcoin {
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@override
|
||||
String getNetworkName(Object wallet) {
|
||||
return (wallet as ElectrumWallet).network.value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setLightningUsername(Object wallet, String username) async {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
await electrumWallet.walletAddresses.setLightningAddress(wallet.name, newAddress: username);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getLightningUsername(Object wallet) async {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
|
||||
if (electrumWallet.walletAddresses.lightningWallet == null) {
|
||||
printV("lightning wallet is null");
|
||||
return null;
|
||||
}
|
||||
return (await electrumWallet.walletAddresses.lightningWallet!.getAddress())
|
||||
?.replaceFirst("@cake.cash", "");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getLightningInvoice(Object wallet, BigInt amount) async {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
|
||||
if (electrumWallet is BitcoinWallet && electrumWallet.lightningWallet != null) {
|
||||
return electrumWallet.lightningWallet!.getBolt11Invoice(amount, "Send to CakeWallet");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String? getBreezSdkError(Object exception) {
|
||||
if (exception is SdkError_SparkError) {
|
||||
return (exception as SdkError_SparkError).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_InvalidUuid) {
|
||||
return (exception as SdkError_InvalidUuid).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_InvalidInput) {
|
||||
return (exception as SdkError_InvalidInput).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_NetworkError) {
|
||||
return (exception as SdkError_NetworkError).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_StorageError) {
|
||||
return (exception as SdkError_StorageError).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_ChainServiceError) {
|
||||
return (exception as SdkError_ChainServiceError).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_LnurlError) {
|
||||
return (exception as SdkError_LnurlError).field0.toString();
|
||||
}
|
||||
if (exception is SdkError_Generic) {
|
||||
return (exception as SdkError_Generic).field0.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:cake_wallet/buy/pairs_utils.dart';
|
||||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_store.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -17,7 +16,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class OnRamperBuyProvider extends BuyProvider {
|
||||
OnRamperBuyProvider(this._themeStore,
|
||||
OnRamperBuyProvider(
|
||||
{required WalletBase wallet, bool isTestEnvironment = false})
|
||||
: super(wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
@@ -39,8 +38,6 @@ class OnRamperBuyProvider extends BuyProvider {
|
||||
static const List<FiatCurrency> _notSupportedFiat = [];
|
||||
static Map<String, dynamic> _onrampMetadata = {};
|
||||
|
||||
final ThemeStore _themeStore;
|
||||
|
||||
String? recommendedPaymentType;
|
||||
|
||||
String get _apiKey => secrets.onramperApiKey;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/core/selectable_option.dart';
|
||||
|
||||
enum PaymentType {
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/widgets/cake_pay_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/cake_pay_states.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||
|
||||
@@ -619,11 +619,16 @@ class CakePayBuyCardPage extends BasePage {
|
||||
titleIconPath: _sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: _sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue: _sendViewModel.pendingTransaction!.amountFormatted,
|
||||
amountValue: _sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
|
||||
_sendViewModel.pendingTransaction!.amountFormatted,
|
||||
_sendViewModel.selectedCryptoCurrency),
|
||||
quantity: 'QTY: ${cakePayBuyCardViewModel.quantity}',
|
||||
fiatAmountValue: _sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue: _sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeValue: "${_sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
|
||||
_sendViewModel.pendingTransaction!.feeFormattedValue,
|
||||
_sendViewModel.selectedCryptoCurrency)} ${_sendViewModel.amountParsingProxy
|
||||
.getCryptoSymbol(_sendViewModel.wallet.currency)}",
|
||||
feeFiatAmount: _sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: displayingOutputs,
|
||||
footerType: FooterType.slideActionButton,
|
||||
@@ -640,6 +645,7 @@ class CakePayBuyCardPage extends BasePage {
|
||||
},
|
||||
change: _sendViewModel.pendingTransaction!.change,
|
||||
isOpenCryptoPay: _sendViewModel.ocpRequest != null,
|
||||
amountParsingProxy: _sendViewModel.amountParsingProxy,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -65,8 +65,7 @@ class CakePayOrder {
|
||||
CakePayPaymentMethod.LTC => order.paymentData.ltc,
|
||||
CakePayPaymentMethod.LTC_MWEB => order.paymentData.ltc_mweb,
|
||||
CakePayPaymentMethod.ZEC => order.paymentData.zec,
|
||||
// TODO: Handle this case.
|
||||
CakePayPaymentMethod.BTC_LN => throw UnimplementedError(),
|
||||
CakePayPaymentMethod.BTC_LN => order.paymentData.btc_ln,
|
||||
};
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
@@ -12,21 +12,29 @@ const AFTER_REGEX = '(\$|\\s)';
|
||||
class AddressValidator extends TextValidator {
|
||||
AddressValidator({required CryptoCurrency type, bool isTestnet = false})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_address,
|
||||
useAdditionalValidation: type == CryptoCurrency.btc || type == CryptoCurrency.ltc
|
||||
? (String txt) => BitcoinAddressUtils.validateAddress(
|
||||
address: txt,
|
||||
network: type == CryptoCurrency.btc
|
||||
? isTestnet
|
||||
? BitcoinNetwork.testnet
|
||||
: BitcoinNetwork.mainnet
|
||||
: LitecoinNetwork.mainnet,
|
||||
)
|
||||
: type == CryptoCurrency.zano
|
||||
? zano?.validateAddress
|
||||
: null,
|
||||
pattern: getPattern(type, isTestnet: isTestnet),
|
||||
length: getLength(type));
|
||||
errorMessage: S.current.error_text_address,
|
||||
useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type)
|
||||
? (String txt) {
|
||||
final RegExp lightningInvoiceRegex = RegExp(
|
||||
r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl)[a-z0-9]+$',
|
||||
caseSensitive: false);
|
||||
if (lightningInvoiceRegex.hasMatch(txt)) return true;
|
||||
|
||||
return BitcoinAddressUtils.validateAddress(
|
||||
address: txt,
|
||||
network: type == CryptoCurrency.btc
|
||||
? isTestnet
|
||||
? BitcoinNetwork.testnet
|
||||
: BitcoinNetwork.mainnet
|
||||
: LitecoinNetwork.mainnet,
|
||||
);
|
||||
}
|
||||
: type == CryptoCurrency.zano
|
||||
? zano?.validateAddress
|
||||
: null,
|
||||
pattern: getPattern(type, isTestnet: isTestnet),
|
||||
length: getLength(type),
|
||||
);
|
||||
|
||||
static String getPattern(CryptoCurrency type, {bool isTestnet = false}) {
|
||||
var pattern = "";
|
||||
@@ -53,6 +61,7 @@ class AddressValidator extends TextValidator {
|
||||
'|(bc1q[ac-hj-np-z02-9]{25,39})'
|
||||
'|(bc1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))'
|
||||
'|(bc1q[ac-hj-np-z02-9]{40,80})'
|
||||
'|(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl)[a-z0-9]+'
|
||||
'|(${silentPaymentAddressPatternMainnet})(\$|\s)';
|
||||
}
|
||||
case CryptoCurrency.ltc:
|
||||
@@ -161,7 +170,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.pivx:
|
||||
pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
case CryptoCurrency.btcln:
|
||||
pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
||||
pattern = r'(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl|LNBC|LNTB|LNBS|LNBCRT|LNURL)[a-zA-Z0-9]+';
|
||||
case CryptoCurrency.zano:
|
||||
pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
|
||||
case CryptoCurrency.doge:
|
||||
@@ -315,6 +324,10 @@ class AddressValidator extends TextValidator {
|
||||
static String get silentPaymentAddressPatternTestnet => '(tsp|sprt)1[0-9a-zA-Z]{113}';
|
||||
static String get mWebAddressPattern => MwebAddress.regex.pattern;
|
||||
|
||||
static const String bolt11InvoiceMatcher = r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt)[a-z0-9]+$';
|
||||
static const String bolt12OfferMatcher = r'^(lightning:)?(lno1)[a-z0-9]+$';
|
||||
static const String lnurlMatcher = r'^(lightning:)?(lnurl)[a-z0-9]+$';
|
||||
|
||||
// NOTE: not needed to check for network here as it's a general address catcher, validation is separate
|
||||
static String? getAddressFromStringPattern(CryptoCurrency type) {
|
||||
String? pattern = null;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// abstract class Amount {
|
||||
// Amount(this.value);
|
||||
|
||||
// int value;
|
||||
|
||||
// int minorDigits;
|
||||
|
||||
// String code;
|
||||
|
||||
// String formatted();
|
||||
// }
|
||||
|
||||
// class MoneroAmount extends Amount {
|
||||
// MoneroAmount(int value) : super(value) {
|
||||
// minorDigits = 12;
|
||||
// code = 'XMR';
|
||||
// }
|
||||
|
||||
// // const moneroAmountLength = 12;
|
||||
// // const moneroAmountDivider = 1000000000000;
|
||||
// // final moneroAmountFormat = NumberFormat()
|
||||
// // ..maximumFractionDigits = moneroAmountLength
|
||||
// // ..minimumFractionDigits = 1;
|
||||
|
||||
// // String moneroAmountToString({int amount}) =>
|
||||
// // moneroAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider));
|
||||
|
||||
// // double moneroAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider);
|
||||
|
||||
// // int moneroParseAmount({String amount}) => moneroAmountFormat.parse(amount).toInt();
|
||||
|
||||
// @override
|
||||
// String formatted() {
|
||||
// // TODO: implement formatted
|
||||
// throw UnimplementedError();
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class AmountParsingProxy {
|
||||
final BitcoinAmountDisplayMode displayMode;
|
||||
|
||||
const AmountParsingProxy(this.displayMode);
|
||||
|
||||
/// [getCryptoInputAmount] turns the input [amount] into the canonical representation of [cryptoCurrency]
|
||||
String getCanonicalCryptoAmount(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) {
|
||||
return cryptoCurrency.formatAmount(BigInt.tryParse(amount) ?? BigInt.zero);
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
/// [getCryptoOutputAmount] turns the input [amount] into the preferred representation of [cryptoCurrency]
|
||||
String getDisplayCryptoAmount(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) {
|
||||
return cryptoCurrency.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals)).toString();
|
||||
}
|
||||
|
||||
return amount.withMaxDecimals(cryptoCurrency.decimals);
|
||||
}
|
||||
|
||||
/// [getCryptoStringRepresentation] turns the input [amount] into the preferred representation of [cryptoCurrency]
|
||||
String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return "$amount";
|
||||
}
|
||||
|
||||
return cryptoCurrency.formatAmount(BigInt.from(amount));
|
||||
}
|
||||
|
||||
String getDisplayCryptoStringFromBigInt(BigInt amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return "$amount";
|
||||
}
|
||||
|
||||
return cryptoCurrency.formatAmount(amount);
|
||||
}
|
||||
|
||||
/// [getCryptoStringFromDouble] turns the input [amount] into the preferred representation of [cryptoCurrency] and
|
||||
String getDisplayCryptoStringFromDouble(double amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return "$amount";
|
||||
}
|
||||
|
||||
return cryptoCurrency.formatAmount(BigInt.from(amount));
|
||||
}
|
||||
|
||||
/// [parseCryptoString] turns the input [string] into a `BigInt` presentation of the [cryptoCurrency]
|
||||
BigInt parseCryptoString(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return BigInt.parse(amount);
|
||||
}
|
||||
|
||||
return cryptoCurrency.parseAmount(amount);
|
||||
}
|
||||
|
||||
/// [tryParseCryptoString] tries to turn the display representation [string] into a `BigInt` presentation of the [cryptoCurrency]
|
||||
BigInt? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return BigInt.tryParse(amount);
|
||||
}
|
||||
|
||||
return cryptoCurrency.tryParseAmount(amount);
|
||||
}
|
||||
|
||||
/// [getCryptoSymbol] returns the correct Symbol related to the presentation
|
||||
String getCryptoSymbol(CryptoCurrency cryptoCurrency) =>
|
||||
useSatoshi(cryptoCurrency) ? "sats" : cryptoCurrency.title.safeSubString(0, 8);
|
||||
|
||||
bool useSatoshi(CryptoCurrency cryptoCurrency) =>
|
||||
([CryptoCurrency.btc, CryptoCurrency.btcln].contains(cryptoCurrency) &&
|
||||
displayMode == BitcoinAmountDisplayMode.satoshi) ||
|
||||
(CryptoCurrency.btcln == cryptoCurrency &&
|
||||
displayMode == BitcoinAmountDisplayMode.satoshiForLightning);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:cw_core/currency.dart';
|
||||
class AmountValidator extends TextValidator {
|
||||
AmountValidator({
|
||||
required CryptoCurrency currency,
|
||||
required AmountParsingProxy amountParsingProxy,
|
||||
bool isAutovalidate = false,
|
||||
String? minValue,
|
||||
String? maxValue,
|
||||
@@ -17,11 +19,15 @@ class AmountValidator extends TextValidator {
|
||||
amountMinValidator = AmountMinValidator(
|
||||
minValue: minValue,
|
||||
isAutovalidate: isAutovalidate,
|
||||
amountParsingProxy: amountParsingProxy,
|
||||
cryptoCurrency: currency,
|
||||
);
|
||||
|
||||
amountMaxValidator = AmountMaxValidator(
|
||||
maxValue: maxValue,
|
||||
isAutovalidate: isAutovalidate,
|
||||
amountParsingProxy: amountParsingProxy,
|
||||
cryptoCurrency: currency,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,10 +95,14 @@ class AllAmountValidator extends TextValidator {
|
||||
class AmountMinValidator extends Validator<String> {
|
||||
final String? minValue;
|
||||
final bool isAutovalidate;
|
||||
final AmountParsingProxy amountParsingProxy;
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
|
||||
AmountMinValidator({
|
||||
this.minValue,
|
||||
required this.isAutovalidate,
|
||||
required this.amountParsingProxy,
|
||||
required this.cryptoCurrency,
|
||||
}) : super(errorMessage: S.current.error_text_input_below_minimum_limit);
|
||||
|
||||
@override
|
||||
@@ -105,6 +115,7 @@ class AmountMinValidator extends Validator<String> {
|
||||
return true;
|
||||
}
|
||||
|
||||
value = amountParsingProxy.getCanonicalCryptoAmount(value, cryptoCurrency);
|
||||
final valueInDouble = parseToDouble(value);
|
||||
final minInDouble = parseToDouble(minValue ?? '');
|
||||
|
||||
@@ -124,10 +135,14 @@ class AmountMinValidator extends Validator<String> {
|
||||
class AmountMaxValidator extends Validator<String> {
|
||||
final String? maxValue;
|
||||
final bool isAutovalidate;
|
||||
final AmountParsingProxy amountParsingProxy;
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
|
||||
AmountMaxValidator({
|
||||
this.maxValue,
|
||||
required this.isAutovalidate,
|
||||
required this.amountParsingProxy,
|
||||
required this.cryptoCurrency,
|
||||
}) : super(errorMessage: S.current.error_text_input_above_maximum_limit);
|
||||
|
||||
@override
|
||||
@@ -140,6 +155,7 @@ class AmountMaxValidator extends Validator<String> {
|
||||
return true;
|
||||
}
|
||||
|
||||
value = amountParsingProxy.getCanonicalCryptoAmount(value, cryptoCurrency);
|
||||
final valueInDouble = parseToDouble(value);
|
||||
final maxInDouble = parseToDouble(maxValue ?? '');
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ Future<void> _handleDuressLogin(
|
||||
|
||||
// Wipe wallet-related database tables
|
||||
try {
|
||||
await db.transaction((txn) async {
|
||||
await db!.transaction((txn) async {
|
||||
await txn.delete(WalletInfoAddressInfo.tableName);
|
||||
await txn.delete(WalletInfoAddressMap.tableName);
|
||||
await txn.delete(WalletInfoAddress.tableName);
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:archive/archive_io.dart' hide Mac;
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/entities/encrypt.dart';
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_backup/backup.dart' as cake_backup;
|
||||
import 'package:cake_wallet/utils/package_info.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
@@ -314,6 +315,7 @@ class BackupServiceV3 extends $BackupService {
|
||||
|
||||
// Delete decrypted data file
|
||||
decryptedData.deleteSync();
|
||||
await initDb();
|
||||
}
|
||||
|
||||
Future<void> verifyHardwareWallets(String password,
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:bech32/bech32.dart';
|
||||
|
||||
Uri decodeLNURL(String encodedUrl) {
|
||||
Uri decodedUri;
|
||||
|
||||
/// The URL doesn't have to be encoded at all as per LUD-17: Protocol schemes and raw (non bech32-encoded) URLs.
|
||||
/// https://github.com/lnurl/luds/blob/luds/17.md
|
||||
/// Handle non bech32-encoded LNURL
|
||||
final lud17prefixes = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth'];
|
||||
decodedUri = Uri.parse(encodedUrl);
|
||||
for (final prefix in lud17prefixes) {
|
||||
if (decodedUri.scheme.contains(prefix)) {
|
||||
decodedUri = decodedUri.replace(scheme: prefix);
|
||||
}
|
||||
}
|
||||
if (lud17prefixes.contains(decodedUri.scheme)) {
|
||||
/// If the non-bech32 LNURL is a Tor address, the port has to be http instead of https for the clearnet LNURL so check if the host ends with '.onion' or '.onion.'
|
||||
decodedUri = decodedUri.replace(
|
||||
scheme: decodedUri.host.endsWith('onion') ||
|
||||
decodedUri.host.endsWith('onion.')
|
||||
? 'http'
|
||||
: 'https');
|
||||
} else {
|
||||
/// Try to parse the input as a lnUrl. Will throw an error if it fails.
|
||||
final lnUrl = _findLnUrl(encodedUrl);
|
||||
|
||||
/// Decode the lnurl using bech32
|
||||
final bech32 = const Bech32Codec().decode(lnUrl, lnUrl.length);
|
||||
decodedUri = Uri.parse(utf8.decode(_convert(bech32.data, 5, 8, false)));
|
||||
}
|
||||
return decodedUri;
|
||||
}
|
||||
|
||||
/// Parse and return a given lnurl string if it's valid. Will remove
|
||||
/// `lightning:` from the beginning of it if present.
|
||||
String _findLnUrl(String input) {
|
||||
final res = RegExp(
|
||||
r',*?((lnurl)([0-9]+[a-z0-9]+))',
|
||||
).allMatches(input.toLowerCase());
|
||||
|
||||
if (res.length == 1) {
|
||||
return res.first.group(0)!;
|
||||
} else {
|
||||
throw ArgumentError('Not a valid lnurl string');
|
||||
}
|
||||
}
|
||||
|
||||
/// Taken from bech32 (bitcoinjs): https://github.com/bitcoinjs/bech32
|
||||
List<int> _convert(List<int> data, int inBits, int outBits, bool pad) {
|
||||
var value = 0;
|
||||
var bits = 0;
|
||||
final maxV = (1 << outBits) - 1;
|
||||
|
||||
final result = <int>[];
|
||||
for (final dataValue in data) {
|
||||
value = (value << inBits) | dataValue;
|
||||
bits += inBits;
|
||||
|
||||
while (bits >= outBits) {
|
||||
bits -= outBits;
|
||||
result.add((value >> bits) & maxV);
|
||||
}
|
||||
}
|
||||
|
||||
if (pad) {
|
||||
if (bits > 0) result.add((value << (outBits - bits)) & maxV);
|
||||
} else {
|
||||
if (bits >= inBits) throw Exception('Excess padding');
|
||||
|
||||
if ((value << (outBits - bits)) & maxV > 0) {
|
||||
throw Exception('Non-zero padding');
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart';
|
||||
import 'package:cake_wallet/core/open_crypto_pay/lnurl.dart';
|
||||
import 'package:cake_wallet/core/open_crypto_pay/models.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/lnurl.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class OpenCryptoPayService {
|
||||
@@ -59,7 +59,7 @@ class OpenCryptoPayService {
|
||||
|
||||
lnUrl = params["lightning"] as String;
|
||||
}
|
||||
final url = decodeLNURL(lnUrl);
|
||||
final url = LNURL.decode(lnUrl);
|
||||
final params = await _getOpenCryptoPayParams(url);
|
||||
|
||||
return OpenCryptoPayRequest(
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
import 'package:cw_core/format_fixed.dart';
|
||||
|
||||
abstract class PaymentURI {
|
||||
PaymentURI({required this.amount, required this.address});
|
||||
|
||||
final String amount;
|
||||
final String address;
|
||||
}
|
||||
|
||||
class MoneroURI extends PaymentURI {
|
||||
MoneroURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'monero:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?tx_amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class HavenURI extends PaymentURI {
|
||||
HavenURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'haven:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?tx_amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class BitcoinURI extends PaymentURI {
|
||||
BitcoinURI({required super.amount, required super.address, this.pjUri = ''});
|
||||
|
||||
final String pjUri;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final qp = <String, String>{};
|
||||
|
||||
if (amount.isNotEmpty) qp['amount'] = amount.replaceAll(',', '.');
|
||||
if (pjUri.isNotEmpty && !address.startsWith("sp")) {
|
||||
qp['pjos'] = '0';
|
||||
qp['pj'] = pjUri;
|
||||
}
|
||||
|
||||
return Uri(scheme: 'bitcoin', path: address, queryParameters: qp).toString();
|
||||
}
|
||||
}
|
||||
|
||||
class LitecoinURI extends PaymentURI {
|
||||
LitecoinURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'litecoin:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class EthereumURI extends PaymentURI {
|
||||
EthereumURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'ethereum:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class BaseURI extends PaymentURI {
|
||||
BaseURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'base:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class ArbitrumURI extends PaymentURI {
|
||||
ArbitrumURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'arbitrum:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class BSCURI extends PaymentURI {
|
||||
BSCURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'bsc:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class BitcoinCashURI extends PaymentURI {
|
||||
BitcoinCashURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = address;
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class NanoURI extends PaymentURI {
|
||||
NanoURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'nano:$address';
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class PolygonURI extends PaymentURI {
|
||||
PolygonURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'polygon:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class SolanaURI extends PaymentURI {
|
||||
SolanaURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'solana:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class TronURI extends PaymentURI {
|
||||
TronURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'tron:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class WowneroURI extends PaymentURI {
|
||||
WowneroURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'wownero:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?tx_amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class ZanoURI extends PaymentURI {
|
||||
ZanoURI({required String amount, required String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'zano:' + address;
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class DecredURI extends PaymentURI {
|
||||
DecredURI({required String amount, required String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'decred:' + address;
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class DogeURI extends PaymentURI {
|
||||
DogeURI({required String amount, required String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'doge:' + address;
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class ZcashURI extends PaymentURI {
|
||||
ZcashURI({required super.amount, required super.address});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'zcash:$address';
|
||||
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class ERC681URI extends PaymentURI {
|
||||
final int chainId;
|
||||
final String? contractAddress;
|
||||
|
||||
ERC681URI({
|
||||
required this.chainId,
|
||||
required super.address,
|
||||
required super.amount,
|
||||
required this.contractAddress,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var uri = 'ethereum:';
|
||||
|
||||
final targetAddress = contractAddress ?? address;
|
||||
uri += targetAddress;
|
||||
|
||||
if (chainId != 1) {
|
||||
uri += '@$chainId';
|
||||
}
|
||||
|
||||
if (contractAddress != null) {
|
||||
uri += '/transfer';
|
||||
}
|
||||
|
||||
final params = <String, String>{};
|
||||
|
||||
if (contractAddress != null) {
|
||||
params['address'] = address;
|
||||
if (amount.isNotEmpty) {
|
||||
params['uint256'] = _formatAmountForERC20(amount);
|
||||
}
|
||||
} else {
|
||||
if (amount.isNotEmpty) {
|
||||
params['value'] = _formatAmountForNative(amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.isNotEmpty) {
|
||||
uri += '?';
|
||||
uri += params.entries.map((e) => '${e.key}=${e.value}').join('&');
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/// Formats amount for ERC-20 token transfers (in atomic units)
|
||||
String _formatAmountForERC20(String amount) {
|
||||
try {
|
||||
// Convert decimal amount to BigInt (assuming 18 decimals)
|
||||
final amountDouble = double.parse(amount.replaceAll(',', '.'));
|
||||
final amountBigInt = BigInt.from(amountDouble * 1e18);
|
||||
return amountBigInt.toString();
|
||||
} catch (e) {
|
||||
// Fallback to original amount if parsing fails
|
||||
return amount.replaceAll(',', '.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats amount for native ETH payments (in wei using scientific notation)
|
||||
String _formatAmountForNative(String amount) {
|
||||
try {
|
||||
// Convert decimal amount to double for scientific notation
|
||||
final amountDouble = double.parse(amount.replaceAll(',', '.'));
|
||||
|
||||
// Use scientific notation as recommended by ERC-681
|
||||
return '${amountDouble}e18';
|
||||
} catch (e) {
|
||||
// Fallback to original amount if parsing fails
|
||||
return amount.replaceAll(',', '.');
|
||||
}
|
||||
}
|
||||
|
||||
factory ERC681URI.fromUri(Uri uri) {
|
||||
final (isContract, targetAddress) = _getTargetAddress(uri.path);
|
||||
final chainId = _getChainID(uri.path);
|
||||
|
||||
final address = isContract ? uri.queryParameters["address"] ?? '' : targetAddress;
|
||||
final amountParam = isContract ? uri.queryParameters["uint256"] : uri.queryParameters["value"];
|
||||
|
||||
var formatedAmount = "";
|
||||
|
||||
if (amountParam != null) {
|
||||
final normalized = _normalizeToIntegerWei(amountParam);
|
||||
formatedAmount = formatFixed(BigInt.parse(normalized), 18);
|
||||
} else {
|
||||
formatedAmount = uri.queryParameters["amount"] ?? "";
|
||||
}
|
||||
|
||||
return ERC681URI(
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
amount: formatedAmount,
|
||||
contractAddress: isContract ? targetAddress : null,
|
||||
);
|
||||
}
|
||||
|
||||
static int _getChainID(String path) {
|
||||
return int.parse(RegExp(
|
||||
r'@\d*',
|
||||
).firstMatch(path)?.group(0)?.replaceAll("@", "") ??
|
||||
"1");
|
||||
}
|
||||
|
||||
static (bool, String) _getTargetAddress(String path) {
|
||||
final targetAddress =
|
||||
RegExp(r'^(0x)?[0-9a-f]{40}', caseSensitive: false).firstMatch(path)!.group(0)!;
|
||||
return (path.contains("/"), targetAddress);
|
||||
}
|
||||
|
||||
/// Normalize an input amount into an integer wei string.
|
||||
///
|
||||
/// Accepts the following forms:
|
||||
/// - Integer string: "123000000000000000" → unchanged
|
||||
/// - Scientific notation: "0.123e18", "1e6" → expanded to integer
|
||||
/// - Decimal ETH: "0.123456" → shifted by 18 decimals
|
||||
static String _normalizeToIntegerWei(String input) {
|
||||
final raw = input.replaceAll(',', '.').trim();
|
||||
|
||||
// First we check if it's already a plain integer (basically just a number with no dot, no exponent)
|
||||
try {
|
||||
final isPlainInteger = RegExp(r'^[+-]?\d+$').hasMatch(raw) &&
|
||||
!raw.contains('.') &&
|
||||
!raw.toLowerCase().contains('e');
|
||||
if (isPlainInteger) return raw.replaceFirst(RegExp(r'^\+'), '');
|
||||
|
||||
// Then we check if it's a scientific notation
|
||||
final sci = RegExp(r'^[+-]?(\d+\.?\d*|\d*\.?\d+)[eE][+-]?\d+$');
|
||||
if (sci.hasMatch(raw)) {
|
||||
final mantissaStr = raw.toLowerCase().split('e')[0];
|
||||
final exp = int.parse(raw.toLowerCase().split('e')[1]);
|
||||
return _expandDecimal(mantissaStr, exp);
|
||||
}
|
||||
|
||||
// Lastly, we check if it's a fixed decimal ETH amount, here we shift by 18 to get wei for the amount
|
||||
if (raw.contains('.')) {
|
||||
return _expandDecimal(raw, 18);
|
||||
}
|
||||
return raw;
|
||||
} catch (e) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
// If none of these checks work, we return the raw input
|
||||
}
|
||||
|
||||
/// Expands a decimal string by shifting the decimal point `expShift` places
|
||||
/// to the right and returns an integer string (digits only, optional leading minus).
|
||||
/// Examples:
|
||||
/// _expandDecimal('0.123456', 18) -> '123456000000000000'
|
||||
/// _expandDecimal('1.2', 3) -> '1200'
|
||||
static String _expandDecimal(String decimalStr, int expShift) {
|
||||
var s = decimalStr.trim();
|
||||
var sign = '';
|
||||
if (s.startsWith('-') || s.startsWith('+')) {
|
||||
sign = s[0] == '-' ? '-' : '';
|
||||
s = s.substring(1);
|
||||
}
|
||||
|
||||
// First we split the integer and fractional parts
|
||||
final parts = s.split('.');
|
||||
final intPart = parts[0].isEmpty ? '0' : parts[0];
|
||||
final fracPart = parts.length > 1 ? parts[1] : '';
|
||||
final digits = (intPart + fracPart).replaceFirst(RegExp(r'^0+'), '');
|
||||
final fracLen = fracPart.length;
|
||||
|
||||
// Then we calculate the effective shift = desired shift minus existing fractional digits
|
||||
final shift = expShift - fracLen;
|
||||
if (shift >= 0) {
|
||||
final head = digits.isEmpty ? '0' : digits;
|
||||
final zeros = List.filled(shift, '0').join();
|
||||
final res = head + zeros;
|
||||
return sign + (res.isEmpty ? '0' : res);
|
||||
} else {
|
||||
// Need to insert a decimal point within digits; return integer by truncating
|
||||
final cut = digits.length + shift;
|
||||
if (cut <= 0) {
|
||||
return '0';
|
||||
}
|
||||
final res = digits.substring(0, cut);
|
||||
return sign + (res.isEmpty ? '0' : res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ String syncStatusTitle(SyncStatus syncStatus, SyncStatusDisplayMode syncStatusDi
|
||||
if (eta.isEmpty) {
|
||||
return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
|
||||
} else {
|
||||
return "${syncStatus.formattedProgress()} - ${S.current.eta} $eta";
|
||||
return "$eta";
|
||||
}
|
||||
} else {
|
||||
return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
|
||||
@@ -42,54 +42,54 @@ String syncStatusTitle(SyncStatus syncStatus, SyncStatusDisplayMode syncStatusDi
|
||||
}
|
||||
|
||||
if (syncStatus is SyncedSyncStatus) {
|
||||
return S.current.sync_status_syncronized;
|
||||
return "";
|
||||
}
|
||||
|
||||
if (syncStatus is FailedSyncStatus) {
|
||||
if (syncStatus.error != null) {
|
||||
return syncStatus.error!;
|
||||
}
|
||||
return S.current.sync_status_failed_connect;
|
||||
}
|
||||
|
||||
if (syncStatus is NotConnectedSyncStatus) {
|
||||
return S.current.sync_status_not_connected;
|
||||
}
|
||||
|
||||
if (syncStatus is AttemptingSyncStatus) {
|
||||
return S.current.sync_status_attempting_sync;
|
||||
}
|
||||
|
||||
if (syncStatus is ConnectingSyncStatus) {
|
||||
return S.current.sync_status_connecting;
|
||||
}
|
||||
|
||||
if (syncStatus is ConnectedSyncStatus) {
|
||||
return S.current.sync_status_connected;
|
||||
return S.current.sync_offline;
|
||||
}
|
||||
|
||||
if (syncStatus is LostConnectionSyncStatus) {
|
||||
return S.current.sync_status_failed_connect;
|
||||
return S.current.sync_offline;
|
||||
}
|
||||
|
||||
if (syncStatus is NotConnectedSyncStatus) {
|
||||
return S.current.sync_not_connected;
|
||||
}
|
||||
|
||||
if (syncStatus is AttemptingSyncStatus) {
|
||||
return S.current.sync_attempting;
|
||||
}
|
||||
|
||||
if (syncStatus is ConnectingSyncStatus) {
|
||||
return S.current.sync_connecting;
|
||||
}
|
||||
|
||||
if (syncStatus is ConnectedSyncStatus) {
|
||||
return S.current.connected;
|
||||
}
|
||||
|
||||
if (syncStatus is UnsupportedSyncStatus) {
|
||||
return S.current.sync_status_unsupported;
|
||||
return S.current.sync_unsupported_node;
|
||||
}
|
||||
|
||||
if (syncStatus is TimedOutSyncStatus) {
|
||||
return S.current.sync_status_timed_out;
|
||||
return S.current.sync_timed_out;
|
||||
}
|
||||
|
||||
if (syncStatus is SyncronizingSyncStatus) {
|
||||
return S.current.sync_status_syncronizing;
|
||||
return S.current.sync_syncing;
|
||||
}
|
||||
|
||||
if (syncStatus is StartingScanSyncStatus) {
|
||||
return S.current.sync_status_starting_scan(syncStatus.beginHeight.toString());
|
||||
return "${S.current.sync_starting_scan} (${(syncStatus.beginHeight.toString())})";
|
||||
}
|
||||
|
||||
if (syncStatus is AttemptingScanSyncStatus) {
|
||||
return S.current.sync_status_attempting_scan;
|
||||
return "Attempting scan";
|
||||
}
|
||||
|
||||
if (syncStatus is ProcessingSyncStatus) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/lnurl.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
|
||||
@@ -73,7 +74,7 @@ class UniversalAddressDetector {
|
||||
|
||||
// Determine currency from scheme
|
||||
final currency = CryptoCurrency.fromString(uri.scheme.toLowerCase());
|
||||
final walletType = cryptoCurrencyToWalletType(currency);
|
||||
final walletType = cryptoCurrencyOrTokenToWalletType(currency);
|
||||
final chainId = getChainIdByCryptoCurrency(currency);
|
||||
|
||||
return AddressDetectionResult(
|
||||
@@ -105,21 +106,27 @@ class UniversalAddressDetector {
|
||||
|
||||
// Detection patterns ordered by specificity (most specific first)
|
||||
final detectionPatterns = [
|
||||
// Lightning Network
|
||||
// Lightning Network (Bolt11 Invoice format)
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^(lnbc|LNBC)[a-km-zA-HJ-NP-Z1-9]{1,}[a-zA-Z0-9]+$'),
|
||||
pattern: RegExp(r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt)[a-z0-9]+$', caseSensitive: false),
|
||||
currency: CryptoCurrency.btcln,
|
||||
),
|
||||
|
||||
|
||||
// Lightning Network (Bolt12 Offer format)
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^(lightning:)?(lno1)[a-z0-9]+$', caseSensitive: false),
|
||||
currency: CryptoCurrency.btcln,
|
||||
),
|
||||
|
||||
// Lightning Address (email format)
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'),
|
||||
currency: CryptoCurrency.btcln,
|
||||
),
|
||||
|
||||
// LNURL format
|
||||
// Lightning Address (LNURL format)
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^(LNURL|lnurl)[a-zA-Z0-9]+$', caseSensitive: false),
|
||||
pattern: RegExp(r'^(lightning:)?(lnurl)[a-z0-9]+$', caseSensitive: false),
|
||||
currency: CryptoCurrency.btcln,
|
||||
),
|
||||
|
||||
@@ -152,7 +159,7 @@ class UniversalAddressDetector {
|
||||
|
||||
// Bitcoin Cash
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^(q|p)[a-z0-9]{41}$'),
|
||||
pattern: RegExp(r'^([qp])[a-z0-9]{41}$'),
|
||||
currency: CryptoCurrency.bch,
|
||||
),
|
||||
|
||||
@@ -212,7 +219,7 @@ class UniversalAddressDetector {
|
||||
|
||||
// Decred
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^(D|T|S)[ksecS][a-km-zA-HJ-NP-Z1-9]+$'),
|
||||
pattern: RegExp(r'^([DTS])[ksecS][a-km-zA-HJ-NP-Z1-9]+$'),
|
||||
currency: CryptoCurrency.dcr,
|
||||
),
|
||||
|
||||
@@ -232,8 +239,9 @@ class UniversalAddressDetector {
|
||||
// Test each pattern in order of specificity
|
||||
for (final pattern in detectionPatterns) {
|
||||
if (pattern.pattern.hasMatch(cleanInput)) {
|
||||
final walletType = cryptoCurrencyToWalletType(pattern.currency);
|
||||
final walletType = cryptoCurrencyOrTokenToWalletType(pattern.currency);
|
||||
final chainId = getChainIdByCryptoCurrency(pattern.currency);
|
||||
final amount = _getAmountFromInvoice(cleanInput, pattern.currency);
|
||||
|
||||
return AddressDetectionResult(
|
||||
address: cleanInput,
|
||||
@@ -241,6 +249,7 @@ class UniversalAddressDetector {
|
||||
detectedWalletType: walletType,
|
||||
isValid: true,
|
||||
chainId: chainId,
|
||||
amount: amount,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -251,6 +260,15 @@ class UniversalAddressDetector {
|
||||
isValid: false,
|
||||
);
|
||||
}
|
||||
|
||||
static String? _getAmountFromInvoice(String input, CryptoCurrency currency) {
|
||||
if (currency != CryptoCurrency.btcln) return null;
|
||||
try {
|
||||
return getBolt11Amount(input).toString();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DetectionPattern {
|
||||
|
||||
@@ -6,4 +6,10 @@ extension EnhancedList<T> on Iterable<T> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String middleTruncate(String s, int head, int tail) {
|
||||
if (s.length <= head + tail + 3) return s;
|
||||
return s.substring(0, head) + '...' + s.substring(s.length - tail);
|
||||
}
|
||||
+271
-145
@@ -6,92 +6,63 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:cake_wallet/anypay/anypay_api.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
|
||||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||
import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/core/backup_service_v3.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/models/cake_pay_vendor.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/services/cake_pay_api.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/services/cake_pay_service.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/backup_service_v3.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
import 'package:cake_wallet/core/node_switching_service.dart';
|
||||
import 'package:cake_wallet/core/reset_service.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/core/selectable_option.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/core/node_switching_service.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/entities/biometric_auth.dart';
|
||||
import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/haven/cw_haven.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart';
|
||||
|
||||
import 'package:cake_wallet/store/dashboard/order_filter_store.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_store.dart';
|
||||
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cake_wallet/view_model/start_tor_view_model.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
|
||||
import 'package:cake_wallet/entities/wallet_manager.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
|
||||
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
|
||||
import 'package:cake_wallet/src/screens/welcome/welcome_page.dart';
|
||||
import 'package:cake_wallet/store/dashboard/payjoin_transactions_store.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/payjoin_details_view_model.dart';
|
||||
import 'package:cw_core/payjoin_session.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
|
||||
import 'package:cake_wallet/entities/wallet_manager.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/haven/cw_haven.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/new-ui/new_dashboard.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/account_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/addresses_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/home_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/receive_page.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/swap_page.dart';
|
||||
import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
@@ -99,7 +70,9 @@ import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dar
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
|
||||
@@ -111,12 +84,25 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/socket_health_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
||||
import 'package:cake_wallet/src/screens/faq/faq_page.dart';
|
||||
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||
@@ -126,7 +112,9 @@ import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
|
||||
@@ -137,20 +125,25 @@ import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/send_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
|
||||
@@ -158,65 +151,36 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||
import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart';
|
||||
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_page.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/animated_ur_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/services/cake_pay_service.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/services/cake_pay_api.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/models/cake_pay_vendor.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_page.dart';
|
||||
import 'package:cake_wallet/src/screens/welcome/welcome_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart';
|
||||
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/order_filter_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/payjoin_transactions_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
|
||||
@@ -227,31 +191,76 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart';
|
||||
import 'package:cake_wallet/store/templates/send_template_store.dart';
|
||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/animated_ur_model.dart';
|
||||
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dev/exchange_provider_logs_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/dev/socket_health_logs_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/order_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/payjoin_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/start_tor_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
@@ -259,19 +268,33 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/payjoin_session.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -280,19 +303,12 @@ import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
|
||||
import 'buy/kryptonim/kryptonim.dart';
|
||||
import 'buy/meld/meld_buy_provider.dart';
|
||||
import 'dogecoin/dogecoin.dart';
|
||||
import 'new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'src/screens/buy/buy_sell_page.dart';
|
||||
import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
import 'package:cake_wallet/core/reset_service.dart';
|
||||
import 'package:cake_wallet/view_model/dev/socket_health_logs_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/socket_health_logs_page.dart';
|
||||
import 'package:cake_wallet/view_model/dev/exchange_provider_logs_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
@@ -374,7 +390,7 @@ Future<void> setup({
|
||||
nodeListStore: getIt.get<NodeListStore>(),
|
||||
themeStore: getIt.get<ThemeStore>()));
|
||||
getIt.registerSingleton<TradesStore>(
|
||||
TradesStore(tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
|
||||
TradesStore(tradesSource: _tradesSource, appStore: getIt.get<AppStore>()));
|
||||
getIt.registerSingleton<OrdersStore>(
|
||||
OrdersStore(ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
|
||||
getIt.registerFactory(() =>
|
||||
@@ -441,6 +457,9 @@ Future<void> setup({
|
||||
newWalletArguments: newWalletArgs,
|
||||
));
|
||||
|
||||
|
||||
final walletList = await WalletInfo.getAll();
|
||||
|
||||
getIt.registerFactory<WalletManager>(
|
||||
() => WalletManager(
|
||||
getIt.get<SharedPreferences>(),
|
||||
@@ -532,7 +551,7 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => BalanceViewModel(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
fiatConvertationStore: getIt.get<FiatConversionStore>()));
|
||||
fiatConversionStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => ExchangeViewModel(
|
||||
@@ -540,7 +559,6 @@ Future<void> setup({
|
||||
_tradesSource,
|
||||
getIt.get<ExchangeTemplateStore>(),
|
||||
getIt.get<TradesStore>(),
|
||||
getIt.get<AppStore>().settingsStore,
|
||||
getIt.get<SharedPreferences>(),
|
||||
getIt.get<ContactListViewModel>(),
|
||||
getIt.get<UnspentCoinsListViewModel>(),
|
||||
@@ -574,8 +592,21 @@ Future<void> setup({
|
||||
sharedPreferences: getIt.get<SharedPreferences>(),
|
||||
keyService: getIt.get<KeyService>()));
|
||||
|
||||
final walletList = await WalletInfo.getAll();
|
||||
|
||||
getIt.registerFactoryParam<CardCustomizerBloc, bool, BitcoinAmountDisplayMode?>(
|
||||
(lightningMode, displayMode) {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
return CardCustomizerBloc(wallet,
|
||||
lightningMode: lightningMode,
|
||||
displaySats: wallet.type == WalletType.bitcoin && (displayMode == BitcoinAmountDisplayMode.satoshi ||
|
||||
(displayMode == BitcoinAmountDisplayMode.satoshiForLightning && lightningMode)));
|
||||
});
|
||||
|
||||
getIt.registerFactory<AccountCreationModal>(() => AccountCreationModal(
|
||||
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>()));
|
||||
|
||||
getIt.registerFactory<LightningUsernameBloc>(
|
||||
() => LightningUsernameBloc(getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory<AuthService>(
|
||||
() => AuthService(
|
||||
secureStorage: getIt.get<SecureStorage>(),
|
||||
@@ -626,6 +657,13 @@ Future<void> setup({
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<LightningUsernamePage, bool?, void>(
|
||||
(isSetup, _) => LightningUsernamePage(
|
||||
isSetup: isSetup ?? false,
|
||||
themeStore: getIt.get<ThemeStore>(),
|
||||
lightningUsernameBloc: getIt.get<LightningUsernameBloc>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<AuthPage>(instanceName: 'login', () {
|
||||
return AuthPage(getIt.get<AuthViewModel>(), closable: false,
|
||||
onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) async {
|
||||
@@ -742,6 +780,13 @@ Future<void> setup({
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<NewDashboard>(() => NewDashboard(
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<NewHomePage>(()=>NewHomePage(dashboardViewModel: getIt.get<DashboardViewModel>(),nftViewModel: getIt.get<NFTViewModel>(),));
|
||||
|
||||
getIt.registerFactory<DesktopSidebarWrapper>(() {
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
return DesktopSidebarWrapper(
|
||||
@@ -802,10 +847,22 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory<ReceivePage>(
|
||||
() => ReceivePage(addressListViewModel: getIt.get<WalletAddressListViewModel>()));
|
||||
getIt.registerFactory<AddressPage>(() => AddressPage(
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>()));
|
||||
|
||||
getIt.registerFactory<AddressPage>(
|
||||
() => AddressPage(
|
||||
addressListViewModel: getIt<WalletAddressListViewModel>(),
|
||||
dashboardViewModel: getIt<DashboardViewModel>(),
|
||||
receiveOptionViewModel: getIt<ReceiveOptionViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NewReceivePage, bool?, CryptoCurrency?>((param1, param2) =>
|
||||
NewReceivePage(
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
lightningMode: param1 ?? false,
|
||||
initialCurrency: param2));
|
||||
|
||||
getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>(
|
||||
(WalletAddressListItem? item, _) =>
|
||||
@@ -816,9 +873,19 @@ Future<void> setup({
|
||||
addressEditOrCreateViewModel:
|
||||
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
getIt.registerFactoryParam<AddressLabelInputPopup, dynamic, void>((dynamic item, _) =>
|
||||
AddressLabelInputPopup(
|
||||
walletAddressEditOrCreateViewModel:
|
||||
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
getIt.registerFactoryParam<ReceiveLabelModal, dynamic, void>((dynamic item, _) =>
|
||||
ReceiveLabelModal(
|
||||
walletAddressEditOrCreateViewModel:
|
||||
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
getIt.registerFactory<SendTemplateViewModel>(() => SendTemplateViewModel(
|
||||
getIt.get<AppStore>().wallet!,
|
||||
getIt.get<AppStore>().settingsStore,
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<SendTemplateStore>(),
|
||||
getIt.get<FiatConversionStore>()));
|
||||
|
||||
@@ -849,6 +916,18 @@ Future<void> setup({
|
||||
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<NewSendPage, SendPageParams?, void>((params, _) {
|
||||
params ??= SendPageParams();
|
||||
return NewSendPage(
|
||||
sendViewModel: getIt.get<SendViewModel>(param1: params.unspentCoinType),
|
||||
authService: getIt.get<AuthService>(),
|
||||
params: params,
|
||||
contactListViewModel: getIt.get<ContactListViewModel>(),
|
||||
paymentViewModel: getIt.get<PaymentViewModel>(),
|
||||
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SendTemplatePage(sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
|
||||
|
||||
@@ -970,7 +1049,7 @@ Future<void> setup({
|
||||
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
DisplaySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<ThemeStore>()));
|
||||
DisplaySettingsViewModel(getIt.get<AppStore>(), getIt.get<ThemeStore>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
@@ -1009,7 +1088,7 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactoryParam<AnimatedURPage, Map<String, String>, void>((Map<String, String> urQr, _) =>
|
||||
@@ -1022,14 +1101,26 @@ Future<void> setup({
|
||||
(CryptoCurrency? cur, _) =>
|
||||
ContactListViewModel(_contactSource, walletList, cur, getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, void>((CryptoCurrency? cur, _) =>
|
||||
ContactListPage(getIt.get<ContactListViewModel>(param1: cur), getIt.get<AuthService>()));
|
||||
getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, bool?>(
|
||||
(CryptoCurrency? cur, bool? showAddContact) => ContactListPage(
|
||||
getIt.get<ContactListViewModel>(param1: cur),
|
||||
getIt.get<AuthService>(),
|
||||
showAddButton: showAddContact ?? true,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
||||
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
||||
|
||||
getIt.registerFactory(() => AddressListPage(getIt.get<WalletAddressListViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<NewAddressesPage, bool, void>(
|
||||
(showHidden, _) => NewAddressesPage(
|
||||
showHidden: showHidden,
|
||||
addressListViewModel: getIt<WalletAddressListViewModel>(),
|
||||
dashboardViewModel: getIt<DashboardViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return NodeListViewModel(_nodeSource, appStore);
|
||||
@@ -1066,27 +1157,40 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(()=> AboutPage(appVersion: getIt.get<SettingsStore>().appVersion));
|
||||
|
||||
getIt.registerFactory(() => NanoChangeRepPage(
|
||||
settingsStore: getIt.get<AppStore>().settingsStore,
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, bool?>(
|
||||
(WalletType? type, bool? isPow) => NodeCreateOrEditViewModel(
|
||||
(isPow ?? false) ? _powNodeSource : _nodeSource,
|
||||
type ?? getIt.get<AppStore>().wallet!.type,
|
||||
getIt.get<SettingsStore>()));
|
||||
getIt.registerFactoryParam<NodeCreateOrEditViewModel, Map<String, dynamic>, void>(
|
||||
(Map<String, dynamic> args, _) {
|
||||
final WalletType type = args['type'] as WalletType? ?? getIt.get<AppStore>().wallet!.type;
|
||||
final bool isPow = args['isPow'] as bool? ?? false;
|
||||
final Node? editingNode = args['editingNode'] as Node?;
|
||||
final nodeSourceArgs = isPow ? _powNodeSource : _nodeSource;
|
||||
return NodeCreateOrEditViewModel(
|
||||
nodeSourceArgs,
|
||||
type,
|
||||
editingNode,
|
||||
getIt.get<SettingsStore>());
|
||||
}
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
|
||||
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
|
||||
(Node? editingNode, bool? isSelected) {
|
||||
final vm = getIt.get<NodeCreateOrEditViewModel>(param1: {'isPow' : false,'editingNode': editingNode});
|
||||
return NodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: vm,
|
||||
editingNode: editingNode,
|
||||
isSelected: isSelected,
|
||||
type: getIt.get<AppStore>().wallet!.type));
|
||||
type: getIt.get<AppStore>().wallet!.type);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
|
||||
(Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: true),
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param1: {'isPow' : true, 'editingNode': editingNode}),
|
||||
editingNode: editingNode,
|
||||
isSelected: isSelected));
|
||||
|
||||
@@ -1111,7 +1215,6 @@ Future<void> setup({
|
||||
));
|
||||
|
||||
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
|
||||
getIt.get<ThemeStore>(),
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
));
|
||||
|
||||
@@ -1148,6 +1251,17 @@ Future<void> setup({
|
||||
return ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>(), paymentRequest);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<NewSwapPage, PaymentRequest?, CryptoCurrency?>(
|
||||
(PaymentRequest? paymentRequest, CryptoCurrency? initialCurrency) {
|
||||
return NewSwapPage(
|
||||
getIt.get<ExchangeViewModel>(),
|
||||
getIt.get<AuthService>(),
|
||||
paymentRequest,
|
||||
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
|
||||
initialCurrency: initialCurrency,
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
@@ -1295,7 +1409,7 @@ Future<void> setup({
|
||||
transactionInfo: transactionInfo,
|
||||
transactionDescriptionBox: _transactionDescriptionBox,
|
||||
wallet: wallet,
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
appStore: getIt.get<AppStore>(),
|
||||
sendViewModel: getIt.get<SendViewModel>(),
|
||||
canReplaceByFee: canReplaceByFee,
|
||||
);
|
||||
@@ -1340,6 +1454,11 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => CakeFeaturesPage(
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => BackupServiceV3(getIt.get<SecureStorage>(),
|
||||
_transactionDescriptionBox,
|
||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||
@@ -1435,7 +1554,7 @@ Future<void> setup({
|
||||
wallet: wallet!,
|
||||
unspentCoinsInfo: _unspentCoinsInfoSource,
|
||||
fiatConversationStore: getIt.get<FiatConversionStore>(),
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
appStore: getIt.get<AppStore>(),
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
|
||||
);
|
||||
});
|
||||
@@ -1445,6 +1564,13 @@ Future<void> setup({
|
||||
unspentCoinsListViewModel:
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
|
||||
|
||||
getIt.registerFactoryParam<NewCoinControlPage, UnspentCoinType?, void>(
|
||||
(coinTypeToSpendFrom, _) => NewCoinControlPage(
|
||||
unspentCoinsListViewModel:
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom))
|
||||
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem,
|
||||
UnspentCoinsListViewModel>(
|
||||
(item, model) =>
|
||||
@@ -1589,17 +1715,17 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => DevSharedPreferencesPage(getIt.get<DevSharedPreferences>()));
|
||||
|
||||
getIt.registerFactory(() => DevSecurePreferencesPage(getIt.get<DevSecurePreferences>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => BackgroundSyncLogsViewModel());
|
||||
|
||||
|
||||
getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => SocketHealthLogsViewModel());
|
||||
|
||||
getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get<SocketHealthLogsViewModel>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => DevNetworkRequests());
|
||||
|
||||
|
||||
getIt.registerFactory(() => DevQRToolsPage());
|
||||
|
||||
getIt.registerFactory(() => ExchangeProviderLogsViewModel());
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:cw_core/enumerable_item.dart';
|
||||
|
||||
class BitcoinAmountDisplayMode extends EnumerableItem<int> with Serializable<int> {
|
||||
const BitcoinAmountDisplayMode({required String title, required int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const all = [
|
||||
BitcoinAmountDisplayMode.satoshi,
|
||||
BitcoinAmountDisplayMode.satoshiForLightning,
|
||||
BitcoinAmountDisplayMode.bitcoin,
|
||||
];
|
||||
static const satoshiForLightning =
|
||||
BitcoinAmountDisplayMode(raw: 0, title: 'sats (LN)');
|
||||
static const bitcoin = BitcoinAmountDisplayMode(raw: 1, title: 'BTC');
|
||||
static const satoshi = BitcoinAmountDisplayMode(raw: 2, title: 'sats');
|
||||
|
||||
static BitcoinAmountDisplayMode deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
case 0:
|
||||
return satoshiForLightning;
|
||||
case 1:
|
||||
return bitcoin;
|
||||
case 2:
|
||||
return satoshi;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw for BalanceDisplayMode deserialize');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => title;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
String calculateFiatAmount({double? price, String? cryptoAmount}) {
|
||||
String calculateFiatAmount({double? price, String? cryptoAmount, bool raw = false}) {
|
||||
if (price == null || cryptoAmount == null) {
|
||||
return '0.00';
|
||||
}
|
||||
@@ -13,6 +13,10 @@ String calculateFiatAmount({double? price, String? cryptoAmount}) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if(raw) {
|
||||
return result.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
var formatted = '';
|
||||
final parts = result.toString().split('.');
|
||||
|
||||
@@ -34,12 +38,16 @@ String formatWithCommas(String? number) {
|
||||
|
||||
final parts = number!.split('.');
|
||||
final integerPart = parts[0];
|
||||
final decimalPart = parts.length > 1 ? parts[1] : '';
|
||||
var decimalPart = parts.length > 1 ? parts[1] : '';
|
||||
|
||||
final formattedInteger = integerPart.replaceAllMapped(
|
||||
RegExp(r'\B(?=(\d{3})+(?!\d))'),
|
||||
(Match match) => ',',
|
||||
);
|
||||
|
||||
if(decimalPart.length == 1) {
|
||||
decimalPart = "${decimalPart}0";
|
||||
}
|
||||
|
||||
return decimalPart.isNotEmpty ? '$formattedInteger.$decimalPart' : formattedInteger;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part 'contact.g.dart';
|
||||
|
||||
@HiveType(typeId: Contact.typeId)
|
||||
class Contact extends HiveObject with Keyable {
|
||||
Contact({required this.name, required this.address, CryptoCurrency? type, DateTime? lastChange})
|
||||
Contact({required this.name, required this.address, CryptoCurrency? type, DateTime? lastChange, this.displayName = ""})
|
||||
: lastChange = lastChange ?? DateTime.now() {
|
||||
if (type != null) {
|
||||
raw = type.raw;
|
||||
@@ -29,6 +29,9 @@ class Contact extends HiveObject with Keyable {
|
||||
@HiveField(3)
|
||||
DateTime lastChange;
|
||||
|
||||
@HiveField(4, defaultValue: "")
|
||||
String displayName;
|
||||
|
||||
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
abstract class ContactBase {
|
||||
ContactBase(this.name, this.address, this.type);
|
||||
ContactBase(this.name, this.address, this.type, {this.displayName = ""});
|
||||
|
||||
String name;
|
||||
|
||||
String address;
|
||||
|
||||
String displayName;
|
||||
|
||||
CryptoCurrency type;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ abstract class ContactRecordBase extends Record<Contact> with Store implements C
|
||||
: name = original.name,
|
||||
address = original.address,
|
||||
type = original.type,
|
||||
displayName = original.displayName,
|
||||
lastChange = original.lastChange,
|
||||
super(source, original);
|
||||
|
||||
@@ -29,6 +30,10 @@ abstract class ContactRecordBase extends Record<Contact> with Store implements C
|
||||
@observable
|
||||
CryptoCurrency type;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String displayName;
|
||||
|
||||
DateTime? lastChange;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show Directory, File, Platform;
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/fs_migration.dart';
|
||||
import 'package:cake_wallet/entities/haven_seed_store.dart';
|
||||
import 'package:cake_wallet/entities/node_list.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cake_wallet/entities/sync_status_display_mode.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/node_list.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/fs_migration.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
@@ -570,7 +575,6 @@ Future<void> defaultSettingsMigration(
|
||||
type: WalletType.zcash,
|
||||
currentNodePreferenceKey: PreferencesKey.currentZcashNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 56:
|
||||
await sharedPreferences.setString(
|
||||
PreferencesKey.syncStatusDisplayMode, SyncStatusDisplayMode.blocksRemaining.name);
|
||||
@@ -586,6 +590,12 @@ Future<void> defaultSettingsMigration(
|
||||
currentNodePreferenceKey: PreferencesKey.currentBscNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 58: // BalanceCardStyleSettings no-op (handled in sqlite.dart)
|
||||
case 59: // WalletInfo.receiveInfoboxDismissed no-op (handled in sqlite.dart)
|
||||
case 60: // BalanceCardStyleSettings.cardOrder no-op (handled in sqlite.dart)
|
||||
// Do not migrate SQLite here, do that in sqlite.dart in order to prevent runtime
|
||||
// errors, missing row and missing tables.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
extension StringExtension on String {
|
||||
String capitalized() {
|
||||
return "${this[0].toUpperCase()}${this.substring(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> generateName() async {
|
||||
final randomThing = Random();
|
||||
final adjectiveStringRaw =
|
||||
await rootBundle.loadString('assets/text/Wallet_Adjectives.txt');
|
||||
final nounStringRaw =
|
||||
await rootBundle.loadString('assets/text/Wallet_Nouns.txt');
|
||||
|
||||
final ls = LineSplitter();
|
||||
final adjectives = ls.convert(adjectiveStringRaw);
|
||||
final nouns = ls.convert(nounStringRaw);
|
||||
|
||||
final chosenAdjective = adjectives[randomThing.nextInt(adjectives.length)];
|
||||
final chosenNoun = nouns[randomThing.nextInt(nouns.length)];
|
||||
final returnString =
|
||||
chosenAdjective.capitalized() + ' ' + chosenNoun.capitalized();
|
||||
return returnString;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ class LanguageService {
|
||||
'es': 'Español (Spanish)',
|
||||
'fa': 'فارسی (Persian)',
|
||||
'fr': 'Français (French)',
|
||||
'gn': 'Guarani (Guarani)',
|
||||
// 'gn': 'Guarani (Guarani)',
|
||||
'hi': 'हिंदी (Hindi)',
|
||||
'ja': '日本 (Japanese)',
|
||||
'ko': '한국어 (Korean)',
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/lnurl.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class LNUrlPayRecord {
|
||||
LNUrlPayRecord({
|
||||
required this.address,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String address;
|
||||
|
||||
static Future<String?> checkWellKnownUsername(String username, CryptoCurrency currency) async {
|
||||
if (![CryptoCurrency.btc, CryptoCurrency.btcln].contains(currency)) return null;
|
||||
|
||||
// split the string by the @ symbol:
|
||||
try {
|
||||
final List<String> splitStrs = username.split("@");
|
||||
String name = splitStrs.first.toLowerCase();
|
||||
final String domain = splitStrs.last;
|
||||
|
||||
if (splitStrs.length == 3) {
|
||||
// for username like @alice@domain.org instead of alice@domain.org
|
||||
name = splitStrs[1];
|
||||
}
|
||||
|
||||
if (name.isEmpty) {
|
||||
name = "_";
|
||||
}
|
||||
|
||||
final expectedUrl = "https://$domain/.well-known/lnurlp/$name";
|
||||
final response = await ProxyWrapper().get(
|
||||
clearnetUri: Uri.parse(expectedUrl),
|
||||
headers: <String, String>{"Accept": "application/json"},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return LNURL.encode(expectedUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
printV("error checking well-known username: $e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String formatDomainName(String name) {
|
||||
String formattedName = name;
|
||||
|
||||
if (name.contains("@")) {
|
||||
formattedName = name.replaceAll("@", ".");
|
||||
}
|
||||
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
static Future<LNUrlPayRecord?> fetchAddressAndName({
|
||||
required String formattedName,
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
final name = formattedName;
|
||||
|
||||
printV("formattedName: $formattedName");
|
||||
|
||||
final address = await checkWellKnownUsername(formattedName, currency);
|
||||
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LNUrlPayRecord(address: address, name: name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewMainActions {
|
||||
final Key key;
|
||||
final String Function(BuildContext context) name;
|
||||
final String image;
|
||||
final VoidCallback onTap;
|
||||
final bool Function(DashboardViewModel viewModel)? isEnabled;
|
||||
final bool Function(DashboardViewModel viewModel)? canShow;
|
||||
|
||||
NewMainActions._({
|
||||
required this.key,
|
||||
required this.name,
|
||||
required this.image,
|
||||
required this.onTap,
|
||||
this.isEnabled,
|
||||
this.canShow,
|
||||
});
|
||||
|
||||
static List<NewMainActions> all = [
|
||||
homeAction,
|
||||
walletsAction,
|
||||
contactsAction,
|
||||
appsAction,
|
||||
//chartsAction,
|
||||
];
|
||||
|
||||
static NewMainActions homeAction = NewMainActions._(
|
||||
name: (context) => S.of(context).home,
|
||||
image: 'assets/new-ui/navbar/home.svg',
|
||||
key: ValueKey('dashboard_page_home_action_button_key'),
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
static NewMainActions walletsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).wallets,
|
||||
image: 'assets/new-ui/navbar/wallets.svg',
|
||||
key: ValueKey('dashboard_page_wallets_action_button_key'),
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
static NewMainActions contactsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).contacts,
|
||||
image: 'assets/new-ui/navbar/contacts.svg',
|
||||
key: ValueKey('dashboard_page_contacts_action_button_key'),
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
static NewMainActions appsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).apps,
|
||||
image: 'assets/new-ui/navbar/apps.svg',
|
||||
key: ValueKey('dashboard_page_apps_action_button_key'),
|
||||
canShow: (dashboardVM) => dashboardVM.showApps,
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
// static NewMainActions chartsAction = NewMainActions._(
|
||||
// name: (context) => 'Charts', //TODO S.of(context).charts,
|
||||
// image: 'assets/new-ui/navbar/charts.svg',
|
||||
// key: ValueKey('dashboard_page_charts_action_button_key'),
|
||||
// onTap: () {},
|
||||
// );
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListItemCheckbox extends ListItem {
|
||||
const ListItemCheckbox({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.iconPath,
|
||||
this.onTap,
|
||||
this.showArrow = false,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final ValueChanged<bool> onChanged;
|
||||
final VoidCallback? onTap;
|
||||
final bool value;
|
||||
final String? subtitle;
|
||||
final String? iconPath;
|
||||
final bool showArrow;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class ListItem {
|
||||
const ListItem({
|
||||
required this.keyValue,
|
||||
required this.label,
|
||||
this.isFirstInSection = false,
|
||||
this.isLastInSection = false,
|
||||
});
|
||||
|
||||
final String keyValue;
|
||||
final String label;
|
||||
|
||||
final bool isFirstInSection;
|
||||
final bool isLastInSection;
|
||||
|
||||
BorderRadius get radius => BorderRadius.vertical(
|
||||
top: Radius.circular(isFirstInSection ? 16 : 0),
|
||||
bottom: Radius.circular(isLastInSection ? 16 : 0),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListItemDropdown extends ListItem {
|
||||
const ListItemDropdown({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.trailingText,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String? trailingText;
|
||||
final VoidCallback onTap;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListItemRegularRow extends ListItem {
|
||||
const ListItemRegularRow({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.trailingText,
|
||||
this.iconPath,
|
||||
this.onTap,
|
||||
this.trailingIconPath,
|
||||
this.showArrow = true,
|
||||
this.truncateTrailingText = false,
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize
|
||||
});
|
||||
|
||||
final String? subtitle;
|
||||
final String? trailingText;
|
||||
final String? iconPath;
|
||||
final String? trailingIconPath;
|
||||
final VoidCallback? onTap;
|
||||
final bool showArrow;
|
||||
final bool truncateTrailingText;
|
||||
final Color? foregroundColor;
|
||||
final double? trailingIconSize;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListItemSelector extends ListItem {
|
||||
const ListItemSelector({
|
||||
required this.options,
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.trailingText,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String? trailingText;
|
||||
final VoidCallback onTap;
|
||||
final List<String> options;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListItemTextField extends ListItem {
|
||||
const ListItemTextField({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.initialValue,
|
||||
this.validator,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.focusNode
|
||||
});
|
||||
|
||||
final String? initialValue;
|
||||
final FormFieldValidator<String>? validator;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final ValueChanged<String>? onFieldSubmitted;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
|
||||
class ListItemToggle extends ListItem {
|
||||
const ListItemToggle({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.leadingEndWidget,
|
||||
});
|
||||
|
||||
final ValueChanged<bool> onChanged;
|
||||
final Widget? leadingEndWidget;
|
||||
final bool value;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/emoji_string_extension.dart';
|
||||
import 'package:cake_wallet/entities/ens_record.dart';
|
||||
import 'package:cake_wallet/entities/lnurlpay_record.dart';
|
||||
import 'package:cake_wallet/entities/fio_address_provider.dart';
|
||||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
@@ -386,6 +387,14 @@ class AddressResolver {
|
||||
return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text);
|
||||
}
|
||||
}
|
||||
|
||||
if (walletType == WalletType.bitcoin) {
|
||||
final record =
|
||||
await LNUrlPayRecord.fetchAddressAndName(formattedName: text, currency: currency);
|
||||
if (record != null) {
|
||||
return ParsedAddress.fetchLNUrlPayAddress(address: record.address, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
|
||||
|
||||
@@ -16,7 +16,8 @@ enum ParseFrom {
|
||||
wellKnown,
|
||||
zanoAlias,
|
||||
zcashAddress,
|
||||
bip353
|
||||
bip353,
|
||||
lnurlpay,
|
||||
}
|
||||
|
||||
class ParsedAddress {
|
||||
@@ -184,6 +185,14 @@ class ParsedAddress {
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchLNUrlPayAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.lnurlpay,
|
||||
);
|
||||
}
|
||||
|
||||
final List<String> addresses;
|
||||
final String name;
|
||||
final String description;
|
||||
|
||||
@@ -26,6 +26,7 @@ class PreferencesKey {
|
||||
static const currentZcashNodeIdKey = 'current_node_id_zec';
|
||||
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||
static const displayAmountsInSatoshi = 'display_amounts_in_satoshi';
|
||||
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
|
||||
static const isAppSecureKey = 'is_app_secure';
|
||||
static const disableTradeOption = 'disable_buy';
|
||||
@@ -62,6 +63,7 @@ class PreferencesKey {
|
||||
static const zanoTransactionPriority = 'current_fee_priority_zano';
|
||||
static const wowneroTransactionPriority = 'current_fee_priority_wownero';
|
||||
static const decredTransactionPriority = 'current_fee_priority_decred';
|
||||
static const zcashTransactionPriority = 'current_fee_priority_decred';
|
||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||
static const mwebCardDisplay = 'mwebCardDisplay';
|
||||
@@ -105,6 +107,8 @@ class PreferencesKey {
|
||||
static const showPayjoinCard = 'show_payjoin_card';
|
||||
static const showCameraConsent = 'show_camera_consent';
|
||||
static const showDecredInfoCard = 'show_decred_info_card';
|
||||
static const forceDecentralizedExchanges = 'force_decentralized_exchanges';
|
||||
static const decentralizedExchangesPromptDismissed = 'decentralized_exchanges_prompt_dismissed';
|
||||
|
||||
static String moneroWalletUpdateV1Key(String name) =>
|
||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||
@@ -131,4 +135,5 @@ class PreferencesKey {
|
||||
static const enableAutomaticNodeSwitching = 'enable_automatic_node_switching';
|
||||
static const syncStatusDisplayMode = 'sync_status_display_mode';
|
||||
static const backgroundImage = 'background_image';
|
||||
static const mwebAdDismissed = "mweb_ad_dismissed";
|
||||
}
|
||||
|
||||
@@ -17,4 +17,8 @@ class WalletContact implements ContactBase {
|
||||
/// Wallet type of the wallet this contact belongs to
|
||||
/// Used for EVM chain filtering
|
||||
final WalletType? walletType;
|
||||
|
||||
String get displayName => "";
|
||||
|
||||
set displayName(String _) {}
|
||||
}
|
||||
|
||||
+20
-1
@@ -238,6 +238,13 @@ class CWEVM extends EVM {
|
||||
) =>
|
||||
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
|
||||
|
||||
@override
|
||||
Future<BigInt?> getAllowance(
|
||||
WalletBase wallet,
|
||||
String tokenContract,
|
||||
String spender) =>
|
||||
(wallet as EVMChainWallet).getAllowance(tokenContract, spender);
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTokenApproval(
|
||||
WalletBase wallet,
|
||||
@@ -265,14 +272,18 @@ class CWEVM extends EVM {
|
||||
String to,
|
||||
String dataHex,
|
||||
BigInt valueWei,
|
||||
TransactionPriority? priority, {
|
||||
TransactionPriority? priority,{
|
||||
bool useBlinkProtection = true,
|
||||
String? sourceTokenAddress,
|
||||
BigInt? sourceTokenAmount,
|
||||
}) =>
|
||||
(wallet as EVMChainWallet).createCallDataTransaction(
|
||||
to,
|
||||
dataHex,
|
||||
valueWei,
|
||||
priority as EVMChainTransactionPriority?,
|
||||
sourceTokenAddress,
|
||||
sourceTokenAmount,
|
||||
useBlinkProtection: useBlinkProtection,
|
||||
);
|
||||
|
||||
@@ -468,6 +479,14 @@ class CWEVM extends EVM {
|
||||
return _registry.getWalletTypeByChainId(chainId);
|
||||
}
|
||||
|
||||
@override
|
||||
BigInt? getERC20AvailableBalance(Object balance) {
|
||||
if(balance is EVMChainERC20Balance) {
|
||||
return balance.balance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ChainInfo> getAllChains() {
|
||||
final allChains = _registry.getAllChains();
|
||||
|
||||
@@ -2,46 +2,47 @@ import 'package:cw_core/enumerable_item.dart';
|
||||
|
||||
class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<int> {
|
||||
const ExchangeProviderDescription(
|
||||
{required String title, required int raw, required this.image, this.horizontalLogo = false})
|
||||
{required String title, required int raw, required this.image, this.horizontalLogo = false, required this.isCentralized})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
final bool horizontalLogo;
|
||||
final String image;
|
||||
final bool isCentralized;
|
||||
|
||||
static const xmrto =
|
||||
ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/images/xmrto.png');
|
||||
ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/new-ui/trade_providers/xmrto.svg', isCentralized: true);
|
||||
static const changeNow =
|
||||
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png');
|
||||
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/new-ui/trade_providers/changenow.svg', isCentralized: true);
|
||||
static const morphToken =
|
||||
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
|
||||
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/new-ui/trade_providers/morph.svg', isCentralized: true);
|
||||
static const sideShift =
|
||||
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
|
||||
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/new-ui/trade_providers/sideshift.svg', isCentralized: true);
|
||||
static const simpleSwap =
|
||||
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
|
||||
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/new-ui/trade_providers/simpleswap.svg', isCentralized: true);
|
||||
static const trocador =
|
||||
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
|
||||
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/new-ui/trade_providers/trocador.svg', isCentralized: true);
|
||||
static const exolix =
|
||||
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
|
||||
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/new-ui/trade_providers/exolix.svg', isCentralized: true);
|
||||
static const all =
|
||||
ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
|
||||
ExchangeProviderDescription(title: 'All trades', raw: 7, image: '', isCentralized: true);
|
||||
static const thorChain =
|
||||
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
|
||||
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/new-ui/trade_providers/thorchain.svg', isCentralized: true);
|
||||
static const swapTrade =
|
||||
ExchangeProviderDescription(title: 'SwapTrade', raw: 9, image: 'assets/images/swap_trade.png');
|
||||
ExchangeProviderDescription(title: 'SwapTrade', raw: 9, image: 'assets/new-ui/trade_providers/swaptrade.svg', isCentralized: true);
|
||||
static const letsExchange =
|
||||
ExchangeProviderDescription(title: 'LetsExchange', raw: 10, image: 'assets/images/letsexchange_icon.svg');
|
||||
ExchangeProviderDescription(title: 'LetsExchange', raw: 10, image: 'assets/new-ui/trade_providers/letsexchange.svg', isCentralized: true);
|
||||
static const stealthEx =
|
||||
ExchangeProviderDescription(title: 'StealthEx', raw: 11, image: 'assets/images/stealthex.png');
|
||||
ExchangeProviderDescription(title: 'StealthEx', raw: 11, image: 'assets/new-ui/trade_providers/stealthex.svg', isCentralized: true);
|
||||
static const chainflip =
|
||||
ExchangeProviderDescription(title: 'Chainflip', raw: 12, image: 'assets/images/chainflip.png');
|
||||
ExchangeProviderDescription(title: 'Chainflip', raw: 12, image: 'assets/new-ui/trade_providers/chainflip.svg', isCentralized: false);
|
||||
static const xoSwap =
|
||||
ExchangeProviderDescription(title: 'XOSwap', raw: 13, image: 'assets/images/xoswap.svg');
|
||||
ExchangeProviderDescription(title: 'XOSwap', raw: 13, image: 'assets/new-ui/trade_providers/xoswap.svg', isCentralized: true);
|
||||
static const swapsXyz =
|
||||
ExchangeProviderDescription(title: 'Swaps.XYZ', raw: 14, image: 'assets/images/swaps_xyz.svg');
|
||||
ExchangeProviderDescription(title: 'Swaps.XYZ', raw: 14, image: 'assets/new-ui/trade_providers/swaps_xyz.svg', isCentralized: false);
|
||||
static const nearIntents =
|
||||
ExchangeProviderDescription(title: 'Near Intents', raw: 15, image: 'assets/images/near.png');
|
||||
ExchangeProviderDescription(title: 'Near Intents', raw: 15, image: 'assets/new-ui/trade_providers/near-intents.svg', isCentralized: false);
|
||||
static const jupiter =
|
||||
ExchangeProviderDescription(title: 'Jupiter', raw: 16, image: 'assets/images/jupiter.png');
|
||||
ExchangeProviderDescription(title: 'Jupiter', raw: 16, image: 'assets/new-ui/trade_providers/jupiter.svg', isCentralized: false);
|
||||
|
||||
static ExchangeProviderDescription deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
|
||||
import 'package:cake_wallet/exchange/trade_request.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
@@ -55,11 +54,11 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
|
||||
if (isFixedRateMode) {
|
||||
params['coinFrom'] = _normalizeCurrency(to);
|
||||
params['coinTo'] = _normalizeCurrency(from);
|
||||
params['coinTo'] = _normalizeCurrency(_overrideFromCryptoCurrency(from));
|
||||
params['networkFrom'] = _networkFor(to);
|
||||
params['networkTo'] = _networkFor(from);
|
||||
} else {
|
||||
params['coinFrom'] = _normalizeCurrency(from);
|
||||
params['coinFrom'] = _normalizeCurrency(_overrideFromCryptoCurrency(from));
|
||||
params['coinTo'] = _normalizeCurrency(to);
|
||||
params['networkFrom'] = _networkFor(from);
|
||||
params['networkTo'] = _networkFor(to);
|
||||
@@ -103,7 +102,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
if (amount == 0) return 0.0;
|
||||
|
||||
final params = {
|
||||
'coinFrom': _normalizeCurrency(from),
|
||||
'coinFrom': _normalizeCurrency(_overrideFromCryptoCurrency(from)),
|
||||
'coinTo': _normalizeCurrency(to),
|
||||
'networkFrom': _networkFor(from),
|
||||
'networkTo': _networkFor(to),
|
||||
@@ -180,6 +179,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
},
|
||||
);
|
||||
printV(e.toString());
|
||||
printV(s.toString());
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
@@ -192,8 +192,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
}) async {
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
final body = {
|
||||
'coinFrom': _normalizeCurrency(request.fromCurrency),
|
||||
'coinTo': _normalizeCurrency(request.toCurrency),
|
||||
'coinFrom': _normalizeCurrency(_overrideFromCryptoCurrency(request.fromCurrency)),
|
||||
'coinTo': _normalizeCurrency(_overrideToCryptoCurrency(request.toCurrency, request.toAddress)),
|
||||
'networkFrom': _networkFor(request.fromCurrency),
|
||||
'networkTo': _networkFor(request.toCurrency),
|
||||
'withdrawalAddress': _normalizeAddress(request.toAddress),
|
||||
@@ -217,7 +217,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
|
||||
if (response.statusCode == 400) {
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final errors = responseJSON['errors'] as Map<String, String>;
|
||||
final errors = responseJSON['error'] as Map<String, String>;
|
||||
final errorMessage = errors.values.join(', ');
|
||||
|
||||
ExchangeProviderLogger.logError(
|
||||
@@ -394,6 +394,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
switch (currency) {
|
||||
case CryptoCurrency.arb:
|
||||
return 'ARBITRUM';
|
||||
case CryptoCurrency.btcln:
|
||||
return 'LIGHTNING';
|
||||
default:
|
||||
return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title;
|
||||
}
|
||||
@@ -406,6 +408,18 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
};
|
||||
}
|
||||
|
||||
CryptoCurrency _overrideFromCryptoCurrency(CryptoCurrency currency) {
|
||||
if (currency == CryptoCurrency.zec)
|
||||
return CryptoCurrency.zaddr; // Sending is always shielded zcash
|
||||
return currency;
|
||||
}
|
||||
|
||||
CryptoCurrency _overrideToCryptoCurrency(CryptoCurrency currency, String address) {
|
||||
if (RegExp(r'u1[a-zA-Z0-9]{100,300}').hasMatch(address) && currency == CryptoCurrency.zec)
|
||||
return CryptoCurrency.zaddr; // If the user pastes a unified address use shielded zcash
|
||||
return currency;
|
||||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
switch (currency) {
|
||||
case CryptoCurrency.nano:
|
||||
@@ -414,6 +428,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
return 'BTT';
|
||||
case CryptoCurrency.zec:
|
||||
return 'ZEC';
|
||||
case CryptoCurrency.zaddr:
|
||||
return 'ZEC-SHIELDED';
|
||||
default:
|
||||
return currency.title;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,16 @@ import 'package:cw_core/crypto_currency.dart';
|
||||
class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
SwapsXyzExchangeProvider();
|
||||
|
||||
static final List<CryptoCurrency> _notSupportedAsSourceToken = [
|
||||
CryptoCurrency.sol,
|
||||
...CryptoCurrency.all.where(
|
||||
(c) => (c.tag ?? '').toUpperCase() == 'SOL',
|
||||
),
|
||||
];
|
||||
|
||||
static const _transferSig = '0xa9059cbb';
|
||||
static const _swapAndExecuteSig = '0x9be111d1';
|
||||
|
||||
static final _apiKey = secrets.swapsXyzApiKey;
|
||||
static const _baseUrl = 'api-v2.swaps.xyz';
|
||||
static const _getChainList = 'api/getChainList';
|
||||
@@ -40,8 +50,10 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
// There is an issue with fixed-rate swaps on Swaps.XYZ
|
||||
// when they don't guarantee that the trade will be created at the requested amount.
|
||||
@override
|
||||
bool get supportsFixedRate => true;
|
||||
bool get supportsFixedRate => false;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description =>
|
||||
@@ -60,22 +72,23 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
final chains = await _geSupportedChain();
|
||||
if (chains.isEmpty) throw Exception('Failed to fetch supported chains');
|
||||
|
||||
final srcChain = _findChainByCurrency(from, chains);
|
||||
final dstChain = _findChainByCurrency(to, chains);
|
||||
final fromToUse = isFixedRateMode ? to : from;
|
||||
final toToUse = isFixedRateMode ? from : to;
|
||||
|
||||
final srcChain = _findChainByCurrency(fromToUse, chains);
|
||||
final dstChain = _findChainByCurrency(toToUse, chains);
|
||||
|
||||
await _ensureTokensCached(
|
||||
fromChain: srcChain, toChain: dstChain, from: from, to: to);
|
||||
fromChain: srcChain, toChain: dstChain, from: fromToUse, to: toToUse);
|
||||
|
||||
final srcToken = _getTokenAddress(currency: from, chain: srcChain);
|
||||
final dstToken = _getTokenAddress(currency: to, chain: dstChain);
|
||||
final srcToken = _getTokenAddress(currency: fromToUse, chain: srcChain);
|
||||
final dstToken = _getTokenAddress(currency: toToUse, chain: dstChain);
|
||||
|
||||
// parameters to and from are swapped in this endpoint
|
||||
// i.e. to get limits for FROM -> TO, we request paths for TO -> FROM
|
||||
final params = {
|
||||
'srcChainId': '${dstChain.chainId}',
|
||||
'srcToken': dstToken,
|
||||
'dstChainId': '${srcChain.chainId}',
|
||||
'dstToken': srcToken,
|
||||
'srcChainId': '${srcChain.chainId}',
|
||||
'srcToken': srcToken,
|
||||
'dstChainId': '${dstChain.chainId}',
|
||||
'dstToken': dstToken,
|
||||
};
|
||||
|
||||
final uri = Uri.https(_baseUrl, _getPaths, params);
|
||||
@@ -86,37 +99,75 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
|
||||
final body = json.decode(res.body) as Map<String, dynamic>;
|
||||
|
||||
final paths = (body['paths'] as List?) ?? const [];
|
||||
final paths =
|
||||
(body['paths'] as List? ?? const []).cast<Map<String, dynamic>>();
|
||||
if (paths.isEmpty) {
|
||||
throw Exception('No paths for ${from.title} -> ${to.title}');
|
||||
throw Exception('No paths for ${fromToUse.title} -> ${toToUse.title}');
|
||||
}
|
||||
|
||||
final path0 = paths.first as Map<String, dynamic>;
|
||||
final tokens = path0['tokens'];
|
||||
final int requestedDstId = dstChain.chainId;
|
||||
|
||||
// If tokens == "all", use path-level amountLimits
|
||||
if (tokens is String && tokens == 'all') {
|
||||
final amountLimits = path0['amountLimits'] as Map<String, dynamic>?;
|
||||
Map<String, dynamic> path = paths.firstWhere(
|
||||
(p) => p['chainId'] == requestedDstId,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
final min =
|
||||
double.tryParse(amountLimits?['minAmount']?.toString() ?? '');
|
||||
final max =
|
||||
double.tryParse(amountLimits?['maxAmount']?.toString() ?? '');
|
||||
|
||||
return Limits(min: min, max: max);
|
||||
if (path.isEmpty) {
|
||||
path = paths.firstWhere(
|
||||
(p) => (p['tokens'] is List) || p['amountLimits'] != null,
|
||||
orElse: () => paths.first,
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise tokens is a list -> find the specific token entry
|
||||
final tokenList = (tokens as List?) ?? const [];
|
||||
final token = findTokenBySymbol(title: from.title, tokens: tokenList);
|
||||
final supportsExactAmountIn =
|
||||
path['supportsExactAmountIn'] as bool? ?? false;
|
||||
final supportsExactAmountOut =
|
||||
path['supportsExactAmountOut'] as bool? ?? false;
|
||||
|
||||
if (token == null) {
|
||||
throw Exception('Token info not found for ${from.title}');
|
||||
if (isFixedRateMode && !supportsExactAmountOut) {
|
||||
throw Exception(
|
||||
'This route does not support fixed receive (exact-amount-out)');
|
||||
}
|
||||
if (!isFixedRateMode && !supportsExactAmountIn) {
|
||||
throw Exception(
|
||||
'This route does not support exact send (exact-amount-in)');
|
||||
}
|
||||
|
||||
final min = double.tryParse(token['minAmount']?.toString() ?? '');
|
||||
final max = double.tryParse(token['maxAmount']?.toString() ?? '');
|
||||
Map<String, dynamic>? useLimits;
|
||||
|
||||
if (isFixedRateMode) {
|
||||
final tokensField = path['tokens'];
|
||||
useLimits = null;
|
||||
|
||||
if (tokensField is List && tokensField.isNotEmpty) {
|
||||
final tokens = tokensField.cast<Map<String, dynamic>>();
|
||||
String norm(String s) => s.toUpperCase();
|
||||
final wantSym = norm(_normalizeCakeNativeTokenName(toToUse.title));
|
||||
final wantAddr = (dstToken).toLowerCase();
|
||||
|
||||
final match = tokens.firstWhere(
|
||||
(t) {
|
||||
final sym = norm(t['symbol']?.toString() ?? '');
|
||||
final addr = (t['address']?.toString() ?? '').toLowerCase();
|
||||
return sym == wantSym || (addr.isNotEmpty && addr == wantAddr);
|
||||
},
|
||||
orElse: () => const <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (match.isNotEmpty) {
|
||||
useLimits = {
|
||||
'minAmount': match['minAmount'],
|
||||
'maxAmount': match['maxAmount'],
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Floating/Exact-in: use the route-level limits
|
||||
useLimits = path['amountLimits'] as Map<String, dynamic>?;
|
||||
}
|
||||
|
||||
final min = double.tryParse((useLimits?['minAmount'])?.toString() ?? '');
|
||||
final max = double.tryParse((useLimits?['maxAmount'])?.toString() ?? '');
|
||||
return Limits(min: min, max: max);
|
||||
} catch (e) {
|
||||
printV('fetchLimits error: $e');
|
||||
@@ -157,6 +208,15 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
required bool isFixedRateMode,
|
||||
required bool isReceiveAmount}) async {
|
||||
try {
|
||||
|
||||
if(_notSupportedAsSourceToken.contains(from)) {
|
||||
printV(
|
||||
'fetchRate: source token ${from.title} is not supported as source token');
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
final chains = await _geSupportedChain();
|
||||
if (chains.isEmpty) return 0.0;
|
||||
|
||||
@@ -248,20 +308,6 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
final dstToken =
|
||||
_getTokenAddress(currency: request.toCurrency, chain: dstChain);
|
||||
|
||||
// Optional: ensure path supports exact-out before attempting fixed rate.
|
||||
if (isFixedRateMode) {
|
||||
final path = await _pickPath(
|
||||
srcChainId: srcChain.chainId,
|
||||
srcToken: srcToken,
|
||||
dstChainId: dstChain.chainId,
|
||||
dstToken: dstToken,
|
||||
);
|
||||
if (path == null || !path.supportsExactOut) {
|
||||
throw Exception(
|
||||
'This route does not support fixed receive (exact-amount-out)');
|
||||
}
|
||||
}
|
||||
|
||||
final amountStr = isFixedRateMode ? request.toAmount : request.fromAmount;
|
||||
final rawAmount = double.tryParse(amountStr) ?? 0.0;
|
||||
if (rawAmount <= 0) throw Exception('Invalid amount');
|
||||
@@ -297,12 +343,28 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
final data = json.decode(res.body) as Map<String, dynamic>;
|
||||
|
||||
final txId = data['txId'] as String? ?? '';
|
||||
|
||||
final vmId = data['vmId'] as String? ?? '';
|
||||
final txObj = (data['tx'] as Map?) ?? const {};
|
||||
|
||||
final txTo = txObj['to']?.toString();
|
||||
final chainId = txObj['chainId']?.toString();
|
||||
final routerData = txObj['data']?.toString();
|
||||
|
||||
// Allow only:
|
||||
// - null (native / deposit-address flow)
|
||||
// - '0x' (no call data)
|
||||
// - ERC20 transfer(0xa9059cbb) selector
|
||||
// - swapAndExecute(0x9be111d1) selector
|
||||
final isAllowed = routerData == null ||
|
||||
routerData == '0x' ||
|
||||
_decodeMethodSelector(routerData) == _transferSig ||
|
||||
_decodeMethodSelector(routerData) == _swapAndExecuteSig;
|
||||
|
||||
if (!isAllowed) {
|
||||
throw Exception('Does not support that method selector');
|
||||
}
|
||||
|
||||
final txValue = txObj['value']?.toString() ?? '0';
|
||||
|
||||
final bridgeIds = (data['bridgeIds'] as List?) ?? const [];
|
||||
@@ -537,57 +599,36 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure tokens for this pair are cached (fills both src & dst chain entries)
|
||||
// Replace your _ensureTokensCached with this one
|
||||
Future<void> _ensureTokensCached({
|
||||
required Chain fromChain,
|
||||
required Chain toChain,
|
||||
required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
}) async {
|
||||
bool needSrc = !_tokensCache.containsKey(fromChain.chainId) ||
|
||||
final needSrc = !_tokensCache.containsKey(fromChain.chainId) ||
|
||||
(_tokensCache[fromChain.chainId]?.isEmpty ?? true);
|
||||
bool needDst = !_tokensCache.containsKey(toChain.chainId) ||
|
||||
|
||||
final needDst = !_tokensCache.containsKey(toChain.chainId) ||
|
||||
(_tokensCache[toChain.chainId]?.isEmpty ?? true);
|
||||
|
||||
if (!needSrc && !needDst) return;
|
||||
|
||||
// First try: from -> to
|
||||
await _fetchAndCacheTokens(
|
||||
srcChainId: fromChain.chainId,
|
||||
srcToken: from.title.toUpperCase(),
|
||||
dstChainId: toChain.chainId,
|
||||
dstToken: to.title.toUpperCase(),
|
||||
);
|
||||
|
||||
// Re-check after first call
|
||||
needSrc = !_tokensCache.containsKey(fromChain.chainId) ||
|
||||
(_tokensCache[fromChain.chainId]?.isEmpty ?? true);
|
||||
needDst = !_tokensCache.containsKey(toChain.chainId) ||
|
||||
(_tokensCache[toChain.chainId]?.isEmpty ?? true);
|
||||
|
||||
// If any still missing, try swapped: to -> from
|
||||
if (needSrc || needDst) {
|
||||
await _fetchAndCacheTokens(
|
||||
srcChainId: toChain.chainId,
|
||||
srcToken: to.title.toUpperCase(),
|
||||
dstChainId: fromChain.chainId,
|
||||
dstToken: from.title.toUpperCase(),
|
||||
);
|
||||
if (needSrc) {
|
||||
await _fetchAndCacheTokens(srcChainId: fromChain.chainId);
|
||||
}
|
||||
if (needDst) {
|
||||
await _fetchAndCacheTokens(srcChainId: toChain.chainId);
|
||||
}
|
||||
}
|
||||
|
||||
// call getPaths and merge tokens into cache keyed by the chainId
|
||||
Future<void> _fetchAndCacheTokens({
|
||||
required int srcChainId,
|
||||
required String srcToken,
|
||||
required int dstChainId,
|
||||
required String dstToken,
|
||||
}) async {
|
||||
final params = <String, String>{
|
||||
'srcChainId': '$srcChainId',
|
||||
'srcToken': srcToken,
|
||||
'dstChainId': '$dstChainId',
|
||||
'srcToken': '0x0000000000000000000000000000000000000000',
|
||||
// Native placeholder
|
||||
};
|
||||
|
||||
final uri = Uri.https(_baseUrl, _getPaths, params);
|
||||
@@ -597,21 +638,71 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
final body = json.decode(res.body) as Map<String, dynamic>;
|
||||
Map<String, dynamic> body;
|
||||
try {
|
||||
body = json.decode(res.body) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
printV('getPaths JSON decode error: $e');
|
||||
return;
|
||||
}
|
||||
|
||||
// Always cache the source chain's native token (from body['srcToken'])
|
||||
final srcTokenJson = body['srcToken'] as Map<String, dynamic>?;
|
||||
if (srcTokenJson != null) {
|
||||
final symbol = (srcTokenJson['symbol'] as String? ?? '').toUpperCase();
|
||||
if (symbol.isNotEmpty) {
|
||||
final isNative = srcTokenJson['isNative'] == true;
|
||||
final decimals = (srcTokenJson['decimals'] as num?)?.toInt();
|
||||
// Treat native token as address = null so _getTokenAddress() emits zero-address
|
||||
final addr = isNative ? null : (srcTokenJson['address'] as String?);
|
||||
_mergeCache(srcChainId, [
|
||||
TokenPathInfo(
|
||||
symbol: symbol,
|
||||
address: addr,
|
||||
decimals: decimals,
|
||||
minAmount: srcTokenJson['minAmount']?.toString(),
|
||||
maxAmount: srcTokenJson['maxAmount']?.toString(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
final paths = (body['paths'] as List?) ?? const [];
|
||||
if (paths.isEmpty) return;
|
||||
|
||||
final path0 = paths.first as Map<String, dynamic>;
|
||||
final pathChainId = (path0['chainId'] as num?)?.toInt();
|
||||
final tokens = (path0['tokens'] as List?) ?? const [];
|
||||
for (final path in paths) {
|
||||
final map = path as Map<String, dynamic>;
|
||||
final pathChainId = (map['chainId'] as num?)?.toInt();
|
||||
if (pathChainId == null) continue;
|
||||
|
||||
if (pathChainId == null || tokens.isEmpty) return;
|
||||
final tokensField = map['tokens'];
|
||||
|
||||
final parsed = tokens
|
||||
.map((t) => TokenPathInfo.fromJson(t as Map<String, dynamic>))
|
||||
.toList();
|
||||
// Case 1: String "all" -> cache empty list to indicate all tokens supported
|
||||
if (tokensField is String) {
|
||||
if (tokensField.toLowerCase() == 'all') {
|
||||
_tokensCache[pathChainId] =
|
||||
_tokensCache[pathChainId] ?? <TokenPathInfo>[];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
_mergeCache(pathChainId, parsed);
|
||||
// Case 2: List -> parse and merge
|
||||
if (tokensField is List) {
|
||||
final parsed = <TokenPathInfo>[];
|
||||
for (final token in tokensField) {
|
||||
if (token is Map<String, dynamic>) {
|
||||
try {
|
||||
parsed.add(TokenPathInfo.fromJson(token));
|
||||
} catch (e) {
|
||||
printV('Token parse error on chain $pathChainId: $e : $token');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parsed.isNotEmpty) {
|
||||
_mergeCache(pathChainId, parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge by symbol, prefer entries that have a non-empty address/decimals
|
||||
@@ -639,52 +730,19 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
_tokensCache[chainId] = bySymbol.values.toList();
|
||||
}
|
||||
|
||||
String _nativeSymbolForChain(Chain chain) {
|
||||
final network = chain.name.toUpperCase();
|
||||
switch (network) {
|
||||
case 'BTC':
|
||||
case 'BITCOIN':
|
||||
return 'BTC';
|
||||
case 'XMR':
|
||||
case 'MONERO':
|
||||
return 'XMR';
|
||||
case 'TRX':
|
||||
case 'TRON':
|
||||
return 'TRX';
|
||||
case 'ETH':
|
||||
case 'ETHEREUM':
|
||||
return 'ETH';
|
||||
case 'LTC':
|
||||
case 'LITECOIN':
|
||||
return 'LTC';
|
||||
case 'DOGE':
|
||||
case 'DOGECOIN':
|
||||
return 'DOGE';
|
||||
case 'XRP':
|
||||
case 'RIPPLE':
|
||||
return 'XRP';
|
||||
case 'XLM':
|
||||
case 'STELLAR':
|
||||
return 'XLM';
|
||||
case 'SOL':
|
||||
case 'SOLANA':
|
||||
return 'SOL';
|
||||
case 'CARDANO':
|
||||
return 'ADA';
|
||||
case 'Bitcoin Cash':
|
||||
return 'BCH';
|
||||
case 'POLYGON':
|
||||
return 'POL';
|
||||
default:
|
||||
return network;
|
||||
}
|
||||
String _normalizeCakeNativeTokenName(String title) {
|
||||
final name = title.toUpperCase();
|
||||
return switch (name) {
|
||||
'ZZEC' => 'ZEC',
|
||||
_ => name,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTokenAddress({
|
||||
required CryptoCurrency currency,
|
||||
required Chain chain,
|
||||
}) {
|
||||
final symbol = currency.title.toUpperCase();
|
||||
final symbol = _normalizeCakeNativeTokenName(currency.title);
|
||||
final list = _tokensCache[chain.chainId];
|
||||
|
||||
// Try cache hit
|
||||
@@ -692,31 +750,17 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
for (final t in list) {
|
||||
if (t.symbol == symbol && t.address != null && t.address!.isNotEmpty) {
|
||||
return t.address!;
|
||||
} else if (t.symbol == symbol && (t.address == null)) {
|
||||
// Native token on this chain
|
||||
return '0x0000000000000000000000000000000000000000';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it's the native coin for this chain, return native placeholder
|
||||
final nativeSym = _nativeSymbolForChain(chain);
|
||||
if (nativeSym == symbol) {
|
||||
return _nativePlaceholderForVm(chain.vmId);
|
||||
}
|
||||
|
||||
// May fail for non-native Alt-VM assets
|
||||
return symbol;
|
||||
}
|
||||
|
||||
String _nativePlaceholderForVm(String vmId) {
|
||||
switch (vmId.toLowerCase()) {
|
||||
case 'evm':
|
||||
return '0x0000000000000000000000000000000000000000';
|
||||
case 'alt-vm':
|
||||
return '0x0000000000000000000000000000000000000000';
|
||||
default:
|
||||
return '0x0000000000000000000000000000000000000000';
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic>? findTokenBySymbol(
|
||||
{required String title, required List<dynamic> tokens}) {
|
||||
final reqSymbol = title.toUpperCase();
|
||||
@@ -731,7 +775,9 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
Chain _findChainByCurrency(CryptoCurrency cur, List<Chain> chains) {
|
||||
final network = _normalizeCakeNetwork(cur.tag ?? cur.title);
|
||||
return chains.firstWhere(
|
||||
(c) => c.name.toUpperCase() == network,
|
||||
(c) {
|
||||
return c.name.toUpperCase() == network;
|
||||
},
|
||||
orElse: () => throw Exception('Unsupported chain for ${cur.title}'),
|
||||
);
|
||||
}
|
||||
@@ -768,6 +814,9 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
final s = str ?? '0';
|
||||
return s.endsWith('n') ? s.substring(0, s.length - 1) : s;
|
||||
}
|
||||
|
||||
String _decodeMethodSelector(String s) =>
|
||||
(s.startsWith('0x') && s.length >= 10) ? s.substring(0, 10) : '';
|
||||
}
|
||||
|
||||
class TokenPathInfo {
|
||||
|
||||
@@ -91,6 +91,7 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
|
||||
}) async {
|
||||
try {
|
||||
if (amount == 0) return 0.0;
|
||||
if(from == CryptoCurrency.btcln || to == CryptoCurrency.btcln) return 0;
|
||||
|
||||
final params = <String, dynamic>{};
|
||||
final body = <String, String>{
|
||||
|
||||
@@ -358,7 +358,13 @@ class XOSwapExchangeProvider extends ExchangeProvider {
|
||||
final payoutAddress = responseJSON['toAddress'] as String;
|
||||
final depositAddress = responseJSON['payInAddress'] as String;
|
||||
final refundAddress = responseJSON['fromAddress'] as String;
|
||||
final depositAmount = _toDouble(amount['value']);
|
||||
final depositAmountStr = amount['value'].toString();
|
||||
final parsedAmount = double.tryParse(depositAmountStr);
|
||||
|
||||
if (parsedAmount == null || parsedAmount <= 0) {
|
||||
throw Exception('Invalid deposit amount received from API');
|
||||
}
|
||||
|
||||
final receiveAmount = toAmount['value'] as String;
|
||||
final status = responseJSON['status'] as String;
|
||||
final createdAtString = responseJSON['createdAt'] as String;
|
||||
@@ -386,7 +392,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
|
||||
'depositAddress': depositAddress,
|
||||
'payoutAddress': payoutAddress,
|
||||
'refundAddress': refundAddress,
|
||||
'depositAmount': depositAmount,
|
||||
'depositAmount': depositAmountStr,
|
||||
'receiveAmount': receiveAmount,
|
||||
'status': status,
|
||||
'createdAt': createdAtString,
|
||||
@@ -405,7 +411,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
|
||||
refundAddress: refundAddress,
|
||||
state: TradeState.deserialize(raw: status),
|
||||
createdAt: createdAt,
|
||||
amount: depositAmount.toString(),
|
||||
amount: depositAmountStr,
|
||||
receiveAmount: receiveAmount.toString(),
|
||||
payoutAddress: payoutAddress,
|
||||
extraId: extraId,
|
||||
@@ -499,7 +505,13 @@ class XOSwapExchangeProvider extends ExchangeProvider {
|
||||
final amount = responseJSON['amount'] as Map<String, dynamic>;
|
||||
final toAmount = responseJSON['toAmount'] as Map<String, dynamic>;
|
||||
final orderId = responseJSON['id'] as String;
|
||||
final depositAmount = amount['value'] as String;
|
||||
final depositAmountStr = amount['value'].toString();
|
||||
final parsedAmount = double.tryParse(depositAmountStr);
|
||||
|
||||
if (parsedAmount == null || parsedAmount <= 0) {
|
||||
throw Exception('Invalid deposit amount received from API');
|
||||
}
|
||||
|
||||
final receiveAmount = toAmount['value'] as String;
|
||||
final depositAddress = responseJSON['payInAddress'] as String;
|
||||
final payoutAddress = responseJSON['toAddress'] as String;
|
||||
@@ -526,7 +538,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
|
||||
refundAddress: refundAddress,
|
||||
state: TradeState.deserialize(raw: status),
|
||||
createdAt: createdAt,
|
||||
amount: depositAmount,
|
||||
amount: depositAmountStr,
|
||||
receiveAmount: receiveAmount,
|
||||
payoutAddress: payoutAddress,
|
||||
extraId: extraId,
|
||||
@@ -554,14 +566,4 @@ class XOSwapExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
double _toDouble(dynamic value) {
|
||||
if (value is int) {
|
||||
return value.toDouble();
|
||||
} else if (value is double) {
|
||||
return value;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:cake_wallet/entities/generate_name.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
|
||||
+16
-10
@@ -1,17 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:cake_wallet/app_scroll_behavior.dart';
|
||||
import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/background_sync.dart';
|
||||
import 'package:cake_wallet/core/node_switching_service.dart';
|
||||
import 'package:cake_wallet/core/reset_service.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/default_settings_migration.dart';
|
||||
import 'package:cake_wallet/entities/get_encryption_key.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/haven_seed_store.dart';
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
@@ -20,39 +22,43 @@ import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/locales/locale.dart';
|
||||
import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/reactions/bootstrap.dart';
|
||||
import 'package:cake_wallet/router.dart' as Router;
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/root/root.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/test_asset_bundles.dart';
|
||||
import 'package:cake_wallet/themes/utils/theme_provider.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/key.dart';
|
||||
import 'package:cw_core/mweb_utxo.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/payjoin_session.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_logger/memory_proxy_logger.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/utils/tor/abstract.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/window_size.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -61,11 +67,8 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:quick_actions/quick_actions.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cw_core/window_size.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
import 'package:cake_wallet/core/reset_service.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
@@ -321,7 +324,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
payjoinSessionSource: payjoinSessionSource,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 57,
|
||||
initialMigrationVersion: 60,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -437,6 +440,9 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
navigatorObservers: [routeObserver, appRouteObserver],
|
||||
navigatorKey: navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
|
||||
child: child!),
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
|
||||
@@ -48,6 +48,10 @@ class CWMoneroAccountList extends MoneroAccountList {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.walletAddresses.accountList
|
||||
.setLabelAccount(accountIndex: accountIndex, label: label);
|
||||
if (accountIndex == moneroWallet.walletAddresses.account?.id) {
|
||||
moneroWallet.walletAddresses.account = monero_account.Account(
|
||||
id: accountIndex, label: label, balance: moneroWallet.walletAddresses.account!.balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +387,7 @@ class CWMonero extends Monero {
|
||||
Future<int> getCurrentHeight() async {
|
||||
return monero_wallet_api.getCurrentHeight();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
bool importKeyImagesUR(Object wallet, String ur) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LongPressPopupBuilder extends StatelessWidget {
|
||||
const LongPressPopupBuilder(
|
||||
{super.key, required this.child, required this.popup, this.spacing = 8});
|
||||
|
||||
final Widget child;
|
||||
final Widget popup;
|
||||
final double spacing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
final RenderBox renderBox = context.findRenderObject() as RenderBox;
|
||||
final offset = renderBox.localToGlobal(Offset.zero);
|
||||
final size = renderBox.size;
|
||||
|
||||
showPopUp(
|
||||
context: context,
|
||||
builder: (context) => _buildPopup(context, offset, size),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPopup(BuildContext context, Offset offset, Size size) {
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: offset.dx,
|
||||
top: offset.dy,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: offset.dx * 2,
|
||||
top: offset.dy + size.height + spacing,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: popup,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:cake_wallet/router.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// allows for full navigation with pages and routes inside a single modal sheet.
|
||||
/// call this in your modal sheet's builder, passing whatever the modal's first page should be to rootPage.
|
||||
/// afterwards you can call pushNamed to push other pages, they'll be created inside the modal.
|
||||
/// you can use the back button (android) or the swipe back gesture (both android and iphone) to go back.
|
||||
/// calling Navigator.of(context).pop() will pop the page INSIDE the modal sheet.
|
||||
/// if you want to pop the whole sheet, use Navigator.of(context, rootNavigator: true).pop().
|
||||
class ModalNavigator extends StatefulWidget {
|
||||
const ModalNavigator({super.key, required this.rootPage, required this.parentContext, this.heightMode = ModalHeightModes.fullScreen});
|
||||
|
||||
final Widget rootPage;
|
||||
final BuildContext parentContext;
|
||||
final ModalHeightModes heightMode;
|
||||
|
||||
@override
|
||||
State<ModalNavigator> createState() => _ModalNavigatorState();
|
||||
}
|
||||
|
||||
enum ModalHeightModes {
|
||||
/// just render as big a modal as possible
|
||||
fullScreen,
|
||||
/// after first frame, read height and lock to that height
|
||||
autoLock,
|
||||
/// let the content size itself. jumps around when pushing different-sized pages!
|
||||
natural
|
||||
}
|
||||
|
||||
class _ModalNavigatorState extends State<ModalNavigator> {
|
||||
final GlobalKey _sheetKey = GlobalKey();
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
double? _sheetHeight;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final context = _sheetKey.currentContext;
|
||||
if (context == null) return;
|
||||
if(_sheetHeight != null) return;
|
||||
|
||||
final box = context.findRenderObject() as RenderBox;
|
||||
setState(() {
|
||||
_sheetHeight = box.size.height;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
late final double? height;
|
||||
switch(widget.heightMode) {
|
||||
case ModalHeightModes.fullScreen:
|
||||
height = MediaQuery.of(context).size.height; break;
|
||||
case ModalHeightModes.autoLock:
|
||||
height = _sheetHeight; break;
|
||||
case ModalHeightModes.natural:
|
||||
height = null; break;
|
||||
}
|
||||
return Container(
|
||||
key:_sheetKey,
|
||||
height: height,
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
pageTransitionsTheme: PageTransitionsTheme(
|
||||
builders: {
|
||||
// requested by ui - iphone-style back anim on every platform
|
||||
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.linux: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.windows: CupertinoPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
),
|
||||
child: PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) return;
|
||||
|
||||
final navigator = _navigatorKey.currentState;
|
||||
if (navigator != null && await navigator.maybePop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(widget.parentContext).pop();
|
||||
},
|
||||
child: Navigator(
|
||||
key: _navigatorKey,
|
||||
onGenerateRoute: (settings) {
|
||||
|
||||
printV(settings.name);
|
||||
|
||||
if (settings.name == "/")
|
||||
return handleRouteWithPlatformAwareness((context) => PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
Navigator.of(widget.parentContext).pop();
|
||||
},
|
||||
child: widget.rootPage),
|
||||
fullscreenDialog: false);
|
||||
else
|
||||
return createRoute(settings);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/home_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/changelog_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/new_main_navbar_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/utils/version_comparator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../view_model/dashboard/dashboard_view_model.dart';
|
||||
|
||||
class NewDashboard extends StatefulWidget {
|
||||
NewDashboard({
|
||||
super.key,
|
||||
required this.dashboardViewModel,
|
||||
required this.bottomSheetService,
|
||||
});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final BottomSheetService bottomSheetService;
|
||||
|
||||
final List<Widget> dashboardPageWidgets = [
|
||||
getIt.get<NewHomePage>(),
|
||||
getIt.get<WalletListPage>(),
|
||||
getIt.get<ContactListPage>(),
|
||||
getIt.get<CakeFeaturesPage>(),
|
||||
Placeholder(),
|
||||
];
|
||||
|
||||
@override
|
||||
State<NewDashboard> createState() => _NewDashboardState();
|
||||
}
|
||||
|
||||
class _NewDashboardState extends State<NewDashboard> {
|
||||
int _selectedPage = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
reaction((_) => widget.dashboardViewModel.appStore.wallet, (_) {
|
||||
setState(() {
|
||||
_selectedPage = 0;
|
||||
});
|
||||
});
|
||||
|
||||
Future.delayed(Duration(milliseconds: 300)).then((_) => _showChangelog(context));
|
||||
_showVulnerableSeedsPopup(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BottomSheetListener(
|
||||
bottomSheetService: widget.bottomSheetService,
|
||||
child: CupertinoScaffold(
|
||||
body: Material(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
// widget.dashboardPageWidgets[_selectedPage],
|
||||
IndexedStack(
|
||||
index: _selectedPage,
|
||||
children: widget.dashboardPageWidgets,
|
||||
),
|
||||
IgnorePointer(
|
||||
child: Container(
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[
|
||||
Theme.of(context).colorScheme.surfaceDim.withAlpha(5),
|
||||
Theme.of(context).colorScheme.surfaceDim.withAlpha(50),
|
||||
Theme.of(context).colorScheme.surfaceDim.withAlpha(100),
|
||||
Theme.of(context).colorScheme.surfaceDim.withAlpha(150),
|
||||
Theme.of(context).colorScheme.surfaceDim.withAlpha(200),
|
||||
Theme.of(context).colorScheme.surfaceDim.withAlpha(200),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
bottom: !(Platform.isIOS),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: NewMainNavBar.barHeight + NewMainNavBar.barBottomPadding,
|
||||
child: AbsorbPointer(
|
||||
absorbing: true,
|
||||
child: Container(color: Colors.transparent),
|
||||
),
|
||||
),
|
||||
),
|
||||
NewMainNavBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
selectedIndex: _selectedPage,
|
||||
onItemTap: (index) {
|
||||
setState(() {
|
||||
_selectedPage = index;
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showChangelog(BuildContext context) async {
|
||||
final sharedPrefs = await SharedPreferences.getInstance();
|
||||
final currentAppVersion = VersionComparator.getExtendedVersionNumber(
|
||||
widget.dashboardViewModel.settingsStore.appVersion);
|
||||
final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion);
|
||||
final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall) ?? true;
|
||||
|
||||
if (currentAppVersion != lastSeenAppVersion && !isNewInstall) {
|
||||
Future<void>.delayed(
|
||||
Duration(seconds: 1),
|
||||
() {
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ChangelogModal(
|
||||
version: widget.dashboardViewModel.settingsStore.appVersion,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
|
||||
} else if (isNewInstall) {
|
||||
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
|
||||
}
|
||||
}
|
||||
|
||||
void _showVulnerableSeedsPopup(BuildContext context) async {
|
||||
final List<String> affectedWalletNames = await widget.dashboardViewModel.checkAffectedWallets();
|
||||
|
||||
if (affectedWalletNames.isNotEmpty) {
|
||||
Future<void>.delayed(
|
||||
Duration(seconds: 1),
|
||||
() {
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return VulnerableSeedsPopup(affectedWalletNames);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
// written by people who happened to read my slack message in 2025
|
||||
// whoever works on this codebase in the future, feel free to add your own mark here
|
||||
const List<String> aboutPageEasterEggs = [
|
||||
"Designed in (S)pain",
|
||||
"Proudly managing over 🤷♂️ XMR",
|
||||
"The cake is not a lie 🍰",
|
||||
"I don’t play soccer because I enjoy the sport. I’m just doing it for kicks.",
|
||||
"Markets in red? Big deal.\nWhat color is the grass outside?",
|
||||
"Conquered Web3, now working on Web6-7",
|
||||
"Proud owner of none of your funds\n(we are not impressed)",
|
||||
"*writing down my seedphrase*\ncake cake cake cake cake cake cake ca...",
|
||||
"Don't forget to actually use your crypto to pay for stuff in the real world 🙂",
|
||||
"A chain of blocks? That's preposterous!",
|
||||
"IOU a hug <3",
|
||||
"Warning: up to 4.8% programmed by cats",
|
||||
"We love collecting your data <3\nWe're just really incompetent at it"
|
||||
];
|
||||
|
||||
class AboutPage extends StatefulWidget {
|
||||
const AboutPage({super.key, required this.appVersion});
|
||||
|
||||
final String appVersion;
|
||||
|
||||
@override
|
||||
State<AboutPage> createState() => _AboutPageState();
|
||||
}
|
||||
|
||||
class _AboutPageState extends State<AboutPage> {
|
||||
static const int easterEggTreshold = 5;
|
||||
String _bottomText = S.current.payment_made_easy;
|
||||
int _easterEggCounter = 0;
|
||||
|
||||
void _easterEgg() {
|
||||
_easterEggCounter++;
|
||||
if(_easterEggCounter == easterEggTreshold) {
|
||||
setState(() {
|
||||
_bottomText = aboutPageEasterEggs.elementAt(Random().nextInt(aboutPageEasterEggs.length));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).about,
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
SizedBox(),
|
||||
GestureDetector(
|
||||
onTap: _easterEgg,
|
||||
child: SvgPicture.asset(
|
||||
"assets/new-ui/cake_squircle_icon.svg",
|
||||
width: 128,
|
||||
height: 128,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
"Cake Wallet",
|
||||
style: TextStyle(fontSize: 32, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(widget.appVersion,
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant))
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Wrap(
|
||||
children: [
|
||||
Text(
|
||||
_bottomText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: NewListSections(sections: {
|
||||
"": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "official website",
|
||||
label: "Official Website",
|
||||
onTap: () => launchUrl(Uri.https("cakewallet.com")),
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconSize: 10),
|
||||
ListItemRegularRow(
|
||||
keyValue: "docs",
|
||||
label: "Cake Docs",
|
||||
onTap: () => launchUrl(Uri.https("docs.cakewallet.com")),
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconSize: 10)
|
||||
],
|
||||
"2": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "gh",
|
||||
label: "GitHub",
|
||||
onTap: () => launchUrl(Uri.https("github.com", "cake-tech")),
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconSize: 10),
|
||||
ListItemRegularRow(
|
||||
keyValue: "twitter",
|
||||
label: "X (Twitter)",
|
||||
onTap: () => launchUrl(Uri.https("twitter.com", "cakewallet")),
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconSize: 10),
|
||||
ListItemRegularRow(
|
||||
keyValue: "tg",
|
||||
label: "Telegram",
|
||||
onTap: () => launchUrl(Uri.https("t.me", "cakewallet")),
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconSize: 10)
|
||||
]
|
||||
}),
|
||||
)
|
||||
],
|
||||
)
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/card_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modal_grab_handle.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cw_core/balance_card_style_settings.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class AccountCustomizerListItem {
|
||||
final BalanceCard card;
|
||||
final int order;
|
||||
final AccountListItem accountListItem;
|
||||
|
||||
AccountCustomizerListItem(
|
||||
{required this.card, required this.order, required this.accountListItem});
|
||||
}
|
||||
|
||||
class AccountCustomizer extends StatefulWidget {
|
||||
const AccountCustomizer(
|
||||
{super.key,
|
||||
required this.accountListViewModel,
|
||||
required this.accountEditOrCreateViewModel,
|
||||
required this.dashboardViewModel});
|
||||
|
||||
final MoneroAccountListViewModel accountListViewModel;
|
||||
final MoneroAccountEditOrCreateViewModel accountEditOrCreateViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
State<AccountCustomizer> createState() => _AccountCustomizerState();
|
||||
}
|
||||
|
||||
class _AccountCustomizerState extends State<AccountCustomizer> {
|
||||
static const double _kStackVisibleFactor = 0.2;
|
||||
late final double cardWidth = MediaQuery.of(context).size.width * 0.9;
|
||||
|
||||
final List<AccountCustomizerListItem> _items = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
loadCards();
|
||||
final activeId = monero!.getCurrentAccount(widget.dashboardViewModel.wallet).id;
|
||||
for (int i = 0; i < _items.length-1; i++) {
|
||||
if(_items[i].accountListItem.id == activeId) {
|
||||
final lastIndex = _items.length - 1;
|
||||
final temp = _items[i];
|
||||
_items[i] = _items[lastIndex];
|
||||
_items[lastIndex] = temp;
|
||||
saveCardOrder();
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
saveCardOrder().then((value) => widget.dashboardViewModel.loadCardDesigns());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void loadCards() {
|
||||
_items.clear();
|
||||
|
||||
final accounts = widget.accountListViewModel.accounts;
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
final index = widget.dashboardViewModel.cardOrder[i];
|
||||
|
||||
if(index == null || index >= accounts.length) {
|
||||
// db order broken.
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: accounts[index].label,
|
||||
accountIndex: accounts[index].id,
|
||||
balance: accounts[index].balance ?? "0.00",
|
||||
accountBalance: accounts[index].balance ?? "0.00",
|
||||
designSwitchDuration: Duration.zero,
|
||||
assetName: widget.accountListViewModel.currency.title,
|
||||
onCustomizeTapped: (i == accounts.length - 1) ? _openCardCustomizer : null,
|
||||
selected: i == accounts.length - 1,
|
||||
width: cardWidth,
|
||||
design: widget.dashboardViewModel.cardDesigns[index],
|
||||
),
|
||||
order: index,
|
||||
accountListItem: accounts[index]));
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_items.isEmpty) return SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).wallet_accounts,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).maybePop,
|
||||
trailingIcon: Icon(Icons.refresh),
|
||||
onTrailingPressed: showResetDialog,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Text(
|
||||
S.of(context).account_customizer_desc,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
ReorderableListView.builder(
|
||||
padding: EdgeInsets.only(bottom: 196),
|
||||
scrollController: ModalScrollController.of(context),
|
||||
onReorder: reorder,
|
||||
proxyDecorator: (child, index, animation) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, _) {
|
||||
final animValue = Curves.easeOutCubic.transform(animation.value);
|
||||
final scale = lerpDouble(1, 1.05, animValue)!;
|
||||
|
||||
return Opacity(
|
||||
opacity: 1 - animValue.clamp(0.0, 0.1),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: cardWidth,
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: _items[index].card,
|
||||
);
|
||||
},
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final card = _items[index].card;
|
||||
|
||||
return Container(
|
||||
key: ValueKey(index),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
reorder(index, _items.length);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _kStackVisibleFactor,
|
||||
child: card,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 50),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
onTap: _showAddAccountModal,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(999999)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
S.of(context).add_account,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _checkReadyToManage() {
|
||||
if (widget.dashboardViewModel.status is! SyncedSyncStatus) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).wallet_is_syncing,
|
||||
alertContent: S.of(context).cannot_manage_accounts_during_sync,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: Navigator.of(context).pop));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _showAddAccountModal() async {
|
||||
if (!_checkReadyToManage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final res = await showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
final modal = getIt.get<AccountCreationModal>();
|
||||
return Material(child: modal);
|
||||
});
|
||||
if (res != null && res is bool && res == true) {
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
loadCards();
|
||||
await saveCardOrder();
|
||||
}
|
||||
}
|
||||
|
||||
void _openCardCustomizer() {
|
||||
if (!_checkReadyToManage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bloc = getIt.get<CardCustomizerBloc>(param1: false);
|
||||
|
||||
Navigator.of(context).push(CupertinoPageRoute(
|
||||
builder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => bloc,
|
||||
child: Material(
|
||||
child: CardCustomizer(
|
||||
cryptoTitle: widget.dashboardViewModel.wallet.currency.fullName ??
|
||||
widget.dashboardViewModel.wallet.currency.name,
|
||||
cryptoName: widget.dashboardViewModel.wallet.currency.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)).then((_) async {
|
||||
bloc.add(DesignSaved());
|
||||
await bloc.stream.firstWhere((item) => item is CardCustomizerSaved);
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
loadCards();
|
||||
});
|
||||
}
|
||||
|
||||
void reorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final AccountCustomizerListItem item = _items.removeAt(oldIndex);
|
||||
_items.insert(newIndex, item);
|
||||
});
|
||||
|
||||
// necessary to copy all this to keep constant constructor for BalanceCard
|
||||
for (int i = 0; i < _items.length; i++) {
|
||||
_items[i] = AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: _items[i].card.accountName,
|
||||
balance: _items[i].card.balance,
|
||||
accountIndex: _items[i].card.accountIndex,
|
||||
accountBalance: _items[i].card.accountBalance,
|
||||
assetName: _items[i].card.assetName,
|
||||
designSwitchDuration: _items[i].card.designSwitchDuration,
|
||||
onCustomizeTapped: (i == _items.length - 1) ? _openCardCustomizer : null,
|
||||
selected: i == _items.length - 1,
|
||||
width: _items[i].card.width,
|
||||
design: _items[i].card.design,
|
||||
),
|
||||
order: i,
|
||||
accountListItem: _items[i].accountListItem);
|
||||
}
|
||||
|
||||
if (newIndex == _items.length - 1 || oldIndex == _items.length - 1) {
|
||||
widget.accountListViewModel.select(_items[_items.length - 1].accountListItem);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveCardOrder() async {
|
||||
for (int i = 0; i < _items.length; i++) {
|
||||
final idx = _items.indexWhere((element) => element.accountListItem.id == i);
|
||||
printV("$i: $idx");
|
||||
|
||||
await BalanceCardStyleSettings.fromCardDesign(
|
||||
widget.dashboardViewModel.wallet.walletInfo.internalId,
|
||||
i,
|
||||
idx,
|
||||
_items[idx].card.design)
|
||||
.insert();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showResetDialog() async {
|
||||
final res = await showPopUp(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).reset,
|
||||
alertContent: S.of(context).card_order_reset_desc,
|
||||
leftButtonText: S.of(context).yes,
|
||||
rightButtonText: S.of(context).no,
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
actionRightButton: Navigator.of(context).pop);
|
||||
});
|
||||
if(res != null && res is bool && res) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
_items.clear();
|
||||
|
||||
final accounts = widget.accountListViewModel.accounts;
|
||||
for (int i = 0; i < widget.accountListViewModel.accounts.length; i++) {
|
||||
|
||||
_items.add(AccountCustomizerListItem(
|
||||
card: BalanceCard(
|
||||
accountName: accounts[i].label,
|
||||
accountIndex: accounts[i].id,
|
||||
balance: accounts[i].balance ?? "0.00",
|
||||
accountBalance: accounts[i].balance ?? "0.00",
|
||||
assetName: widget.accountListViewModel.currency.title,
|
||||
selected: true,
|
||||
designSwitchDuration: Duration(milliseconds: 200),
|
||||
width: cardWidth,
|
||||
design: widget.dashboardViewModel.cardDesigns[i],
|
||||
),
|
||||
order: i,
|
||||
accountListItem: accounts[i]));
|
||||
}
|
||||
|
||||
saveCardOrder();
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
class AccountCreationModal extends StatefulWidget {
|
||||
const AccountCreationModal({super.key, required this.accountEditOrCreateViewModel});
|
||||
|
||||
final MoneroAccountEditOrCreateViewModel accountEditOrCreateViewModel;
|
||||
|
||||
@override
|
||||
State<AccountCreationModal> createState() => _AccountCreationModalState();
|
||||
}
|
||||
|
||||
class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(30))),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalGrabHandle(),
|
||||
ModalTopBar(title: S.of(context).create_account),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
spacing: 50,
|
||||
children: [
|
||||
SizedBox(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
decoration: InputDecoration(hintText: S.of(context).account_name),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
_controller.text = await generateName();
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: SvgPicture.asset(
|
||||
"assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
NewPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_loading) return;
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
widget.accountEditOrCreateViewModel.label = _controller.text;
|
||||
await widget.accountEditOrCreateViewModel.save();
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: _loading,
|
||||
),
|
||||
SizedBox(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/new-ui/long_press_popup.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/long_press_menu.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/utils/address_formatter.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class NewAddressesPage extends StatefulWidget {
|
||||
const NewAddressesPage(
|
||||
{super.key,
|
||||
required this.addressListViewModel,
|
||||
required this.dashboardViewModel,
|
||||
required this.showHidden});
|
||||
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final bool showHidden;
|
||||
|
||||
@override
|
||||
State<NewAddressesPage> createState() => _NewAddressesPageState();
|
||||
}
|
||||
|
||||
class _NewAddressesPageState extends State<NewAddressesPage> {
|
||||
List<ListItem> getItems(List<ListItem> list) {
|
||||
return list.where((element) {
|
||||
if (element is WalletAddressListItem) {
|
||||
if (!element.isHidden && !widget.showHidden) return true;
|
||||
if (element.isHidden && widget.showHidden) return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<ListItem> items = [];
|
||||
|
||||
late final TextEditingController _searchController;
|
||||
|
||||
CardDesign? design;
|
||||
|
||||
void updateItems() {
|
||||
setState(() {
|
||||
items = getItems(widget.addressListViewModel.forceRecomputeItems);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
items = getItems(widget.addressListViewModel.items);
|
||||
_searchController = TextEditingController()
|
||||
..addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
reaction((context) => widget.dashboardViewModel.cardDesigns.first, (value) {
|
||||
setState(() {
|
||||
design = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteredItems = items.where((item) {
|
||||
if (item is! WalletAddressListItem) return false;
|
||||
if (_searchController.text.isEmpty) return true;
|
||||
if (item.name == null || item.name!.isEmpty) {
|
||||
return (item.address.contains(_searchController.text.toLowerCase()));
|
||||
}
|
||||
return (item.name ?? '').toLowerCase().contains(_searchController.text.toLowerCase());
|
||||
}).toList();
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: widget.showHidden ? S.of(context).hidden_addresses : S.of(context).addresses,
|
||||
leadingIcon: Icon(Icons.arrow_back),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
onTrailingPressed: () {}),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
slivers: [
|
||||
if (!widget.showHidden)
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
if (widget.dashboardViewModel.type == WalletType.monero ||
|
||||
widget.dashboardViewModel.type == WalletType.wownero)
|
||||
Observer(
|
||||
builder: (_) => AccountPreviewHeader(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
design: design,
|
||||
)),
|
||||
Text(
|
||||
S.of(context).long_press_edit_address,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
ShowHiddenButton()
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: 64),
|
||||
sliver: SliverList.separated(
|
||||
itemCount: filteredItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = filteredItems[index];
|
||||
if (item is WalletAddressListItem) {
|
||||
return Observer(
|
||||
builder: (_) => AddressRow(
|
||||
selected: item.address == widget.addressListViewModel.address.address,
|
||||
first: widget.showHidden && index == 0,
|
||||
last: index == filteredItems.length - 1,
|
||||
item: item,
|
||||
onSelect: () {
|
||||
widget.addressListViewModel.setAddress(item);
|
||||
},
|
||||
onLabelChanged: updateItems,
|
||||
onAddressHidden: () async {
|
||||
await widget.addressListViewModel.toggleHideAddress(item);
|
||||
updateItems();
|
||||
},
|
||||
walletType: widget.addressListViewModel.type,
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox.shrink();
|
||||
},
|
||||
separatorBuilder: (_, __) => Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: 72))
|
||||
],
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: AddressSearchBox(controller: _searchController)),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressSearchBox extends StatelessWidget {
|
||||
const AddressSearchBox({super.key, required this.controller});
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 18.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh.withAlpha(128),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest, width: 1),
|
||||
borderRadius: BorderRadius.circular(99999)),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).search,
|
||||
hintStyle: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
prefixIcon: Icon(Icons.search),
|
||||
filled: false,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(99999),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(99999),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountPreviewHeader extends StatelessWidget {
|
||||
AccountPreviewHeader({super.key, required this.dashboardViewModel, required this.design});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final CardDesign? design;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 64,
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => BalanceCard(
|
||||
borderRadius: 5, width: 50, design: design ?? CardDesign.genericDefault),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
monero?.getCurrentAccount(dashboardViewModel.wallet).label ??
|
||||
wownero?.getCurrentAccount(dashboardViewModel.wallet).label ??
|
||||
"",
|
||||
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
Text(
|
||||
dashboardViewModel.wallet.name,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
Container(
|
||||
width: 1,
|
||||
height: 40,
|
||||
decoration:
|
||||
BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerHigh),
|
||||
),
|
||||
Text(dashboardViewModel.balanceViewModel.balances.values.first.availableBalance)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressRow extends StatelessWidget {
|
||||
const AddressRow(
|
||||
{super.key,
|
||||
required this.selected,
|
||||
required this.first,
|
||||
required this.last,
|
||||
required this.item,
|
||||
required this.onSelect,
|
||||
required this.walletType,
|
||||
required this.onLabelChanged,
|
||||
required this.onAddressHidden});
|
||||
|
||||
final bool selected;
|
||||
final bool first;
|
||||
final bool last;
|
||||
final WalletAddressListItem item;
|
||||
final VoidCallback onSelect;
|
||||
final VoidCallback onLabelChanged;
|
||||
final VoidCallback onAddressHidden;
|
||||
final WalletType walletType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasLabel = item.name != null && item.name!.isNotEmpty;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onSelect,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: LongPressPopupBuilder(
|
||||
popup: LongPressMenu(
|
||||
items: [
|
||||
LongPressMenuItem(
|
||||
label: S.of(context).set_label,
|
||||
iconPath: "assets/new-ui/address_set_label.svg",
|
||||
onSelected: () async {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
final res = await showPopUp(
|
||||
context: context,
|
||||
builder: (context) => getIt.get<AddressLabelInputPopup>(param1: item));
|
||||
|
||||
if (res != null) {
|
||||
onLabelChanged();
|
||||
}
|
||||
}),
|
||||
LongPressMenuItem(
|
||||
label: item.isHidden ? S.of(context).unhide_address : S.of(context).hide_address,
|
||||
iconPath: "assets/new-ui/address_hide.svg",
|
||||
onSelected: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
onAddressHidden();
|
||||
},
|
||||
color: Theme.of(context).colorScheme.error),
|
||||
],
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? Theme.of(context).colorScheme.surfaceContainerHigh
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(first ? 16 : 0),
|
||||
bottom: Radius.circular(last ? 16 : 0),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
if (hasLabel)
|
||||
Text(
|
||||
item.name!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
Expanded(
|
||||
child: AddressFormatter.buildSegmentedAddress(
|
||||
address: item.address,
|
||||
walletType: walletType,
|
||||
textAlign: TextAlign.left,
|
||||
evenTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: hasLabel
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
shouldTruncate: (hasLabel)),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${S.of(context).transactions}: ${item.txCount}",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
Text("${S.of(context).balance}: ${item.balance}",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (selected) SvgPicture.asset("assets/new-ui/checkmark.svg")
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShowHiddenButton extends StatelessWidget {
|
||||
const ShowHiddenButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.receiveAddresses, arguments: true);
|
||||
},
|
||||
child: Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).show_hidden_addresses),
|
||||
RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: SvgPicture.asset("assets/new-ui/dropdown_arrow.svg"))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CardCustomizer extends StatefulWidget {
|
||||
const CardCustomizer({super.key, required this.cryptoTitle, required this.cryptoName});
|
||||
|
||||
final String cryptoTitle;
|
||||
final String cryptoName;
|
||||
|
||||
@override
|
||||
State<CardCustomizer> createState() => _CardCustomizerState();
|
||||
}
|
||||
|
||||
class _CardCustomizerState extends State<CardCustomizer> {
|
||||
final accountNameController = TextEditingController();
|
||||
bool editEnabled = false;
|
||||
late final CardCustomizerBloc bloc;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
bloc = context.read<CardCustomizerBloc>();
|
||||
|
||||
if (bloc.state is! CardCustomizerNotLoaded) {
|
||||
editEnabled = bloc.state.accountName.isNotEmpty;
|
||||
accountNameController.text = bloc.state.accountName;
|
||||
} else {
|
||||
late final StreamSubscription sub;
|
||||
sub = bloc.stream.listen((state) {
|
||||
if (state is! CardCustomizerNotLoaded) {
|
||||
setState(() {
|
||||
editEnabled = state.accountName.isNotEmpty;
|
||||
});
|
||||
sub.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<CardCustomizerBloc, CardCustomizerState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.accountName != current.accountName,
|
||||
listener: (context, state) {
|
||||
if(accountNameController.text != state.accountName) {
|
||||
accountNameController.text = state.accountName;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<CardCustomizerBloc, CardCustomizerState>(
|
||||
builder: (context, state) {
|
||||
if (state is CardCustomizerNotLoaded) return SizedBox.shrink();
|
||||
return PopScope(
|
||||
child: SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
spacing: 25.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: editEnabled ? S.of(context).edit_account : S.of(context).edit_card,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
// trailingIcon: editEnabled ? Icon(Icons.delete_forever) : null,
|
||||
onLeadingPressed: () => Navigator.of(context).maybePop(),
|
||||
// onTrailingPressed: () {},
|
||||
),
|
||||
if (editEnabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).account_name),
|
||||
TextField(
|
||||
maxLength: 32,
|
||||
decoration: InputDecoration(counterText: ""),
|
||||
onChanged: (value) {
|
||||
context.read<CardCustomizerBloc>().add(AccountNameChanged(value));
|
||||
},
|
||||
controller: accountNameController,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BalanceCard(
|
||||
width: min(MediaQuery.of(context).size.width * 0.87, 768),
|
||||
selected: true,
|
||||
designSwitchDuration: Duration(milliseconds: 300),
|
||||
accountName:
|
||||
editEnabled ? state.accountName:"" ,
|
||||
balance: "0.00",
|
||||
assetName: state.displaySats ? "sats" : widget.cryptoName,
|
||||
capitalizeAssetName: !state.displaySats,
|
||||
design: state.selectedDesign,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).card_style),
|
||||
Container(
|
||||
height: 63,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.availableDesigns.length,
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(width: 8.0);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(CardDesignSelected(index));
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
side: BorderSide(color: ((index == state.selectedDesignIndex )? Theme.of(context).colorScheme.onSurface : Colors.transparent), width: 1),
|
||||
borderRadius: BorderRadiusGeometry.circular(12),
|
||||
),
|
||||
),
|
||||
child: AnimatedScale(
|
||||
duration: Duration(milliseconds: 200),
|
||||
scale: index == state.selectedDesignIndex ? 0.94 : 1,
|
||||
child: BalanceCard(
|
||||
width: 96,
|
||||
borderRadius: 10,
|
||||
selected: false,
|
||||
designSwitchDuration: Duration(milliseconds: 300),
|
||||
design: state.availableDesigns[index],
|
||||
gradient: state.selectedDesign.gradient,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
)),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).color),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 4, // space between items in a row
|
||||
runSpacing: 8,
|
||||
children:
|
||||
List.generate(state.availableColors.length, (index) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(999999999),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999999999),
|
||||
onTap: () {
|
||||
context
|
||||
.read<CardCustomizerBloc>()
|
||||
.add(ColorSelected(index));
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedOpacity(
|
||||
duration: Duration(milliseconds: 200),
|
||||
opacity: index == state.selectedColorIndex ? 1 : 0,
|
||||
child: Container(
|
||||
width:32,height:32,decoration: BoxDecoration(borderRadius: BorderRadius.circular(99999999),border: Border.all(color:Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface))
|
||||
),
|
||||
),
|
||||
AnimatedScale(
|
||||
duration: Duration(milliseconds: 200),
|
||||
scale: index == state.selectedColorIndex ? 0.8 : 1,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(99999999),
|
||||
gradient: state.availableColors[index]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coin_control_page/coin_control_list_item.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import "package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart";
|
||||
|
||||
class NewCoinControlPage extends StatefulWidget {
|
||||
const NewCoinControlPage({super.key, required this.unspentCoinsListViewModel});
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
State<NewCoinControlPage> createState() => _NewCoinControlPageState();
|
||||
}
|
||||
|
||||
class _NewCoinControlPageState extends State<NewCoinControlPage> {
|
||||
late Future<void> _initialization;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initialization = widget.unspentCoinsListViewModel.initialSetup();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: !widget.unspentCoinsListViewModel.isSavingItems,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: "",
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onTrailingPressed: () {}),
|
||||
FutureBuilder(
|
||||
future: _initialization,
|
||||
builder: (context, asyncSnapshot) {
|
||||
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
|
||||
return Expanded(
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 12,
|
||||
children: [
|
||||
CupertinoActivityIndicator(),
|
||||
Text("${S.of(context).loading}...")
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
if (asyncSnapshot.hasError)
|
||||
return Center(child: Text(S.of(context).coin_control_load_failed));
|
||||
|
||||
return Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: ModalHeader(
|
||||
iconPath: "assets/new-ui/settings_row_icons/coin-control.svg",
|
||||
title: "Coin Control",
|
||||
message: S.of(context).coin_control_desc),
|
||||
),
|
||||
if (widget.unspentCoinsListViewModel.items.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
spacing: 20,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
widget.unspentCoinsListViewModel.toggleSelectAll(true);
|
||||
},
|
||||
child: Text(S.of(context).select_all,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400)),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
widget.unspentCoinsListViewModel.toggleSelectAll(false);
|
||||
},
|
||||
child: Text(S.of(context).unselect_all,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.unspentCoinsListViewModel.nonFrozenItems.isEmpty &&
|
||||
widget.unspentCoinsListViewModel.frozenItems.isEmpty) ...[
|
||||
SizedBox(height: 12),
|
||||
Center(
|
||||
child: Text(
|
||||
S.of(context).no_unspent_coins,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)),
|
||||
],
|
||||
Observer(
|
||||
builder: (_) => widget
|
||||
.unspentCoinsListViewModel.nonFrozenItems.isEmpty
|
||||
? SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: CoinControlListSection(
|
||||
items: widget.unspentCoinsListViewModel.nonFrozenItems,
|
||||
unspentCoinsListViewModel:
|
||||
widget.unspentCoinsListViewModel),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (context) =>
|
||||
widget.unspentCoinsListViewModel.frozenItems.isEmpty
|
||||
? SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
S.of(context).frozen,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant),
|
||||
),
|
||||
CoinControlListSection(
|
||||
items: widget
|
||||
.unspentCoinsListViewModel.frozenItems,
|
||||
unspentCoinsListViewModel:
|
||||
widget.unspentCoinsListViewModel),
|
||||
]),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CoinControlListSection extends StatelessWidget {
|
||||
const CoinControlListSection(
|
||||
{super.key, required this.items, required this.unspentCoinsListViewModel});
|
||||
|
||||
final List<UnspentCoinsItem> items;
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
itemBuilder: (_, int index) {
|
||||
return Observer(builder: (_) {
|
||||
final item = items[index];
|
||||
final fiatAmount = unspentCoinsListViewModel.fiatAmounts[item.amount] ?? '';
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.unspentCoinsDetails,
|
||||
arguments: [item, unspentCoinsListViewModel],
|
||||
),
|
||||
child: CoinControlListItem(
|
||||
note: item.note,
|
||||
amount: item.amount,
|
||||
fiatAmount: fiatAmount,
|
||||
address: item.address,
|
||||
isSending: item.isSending,
|
||||
isFrozen: item.isFrozen,
|
||||
isChange: item.isChange,
|
||||
isSilentPayment: item.isSilentPayment,
|
||||
isLoading: item.isBeingSaved,
|
||||
isFirst: index == 0,
|
||||
isLast: index == items.length - 1,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
item.isSending = !item.isSending;
|
||||
await unspentCoinsListViewModel.saveUnspentCoinInfo(item);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/account_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/card_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/settings_page.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/action_row/coin_action_row.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/assets_history_section.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/cards_view.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/mweb_ad.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/wallet_info.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class NewHomePage extends StatefulWidget {
|
||||
NewHomePage({super.key, required this.dashboardViewModel, required this.nftViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final NFTViewModel nftViewModel;
|
||||
|
||||
@override
|
||||
State<NewHomePage> createState() => _NewHomePageState();
|
||||
}
|
||||
|
||||
class _NewHomePageState extends State<NewHomePage> {
|
||||
MoneroAccountListViewModel? accountListViewModel;
|
||||
bool _lightningMode = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setAccountViewModel();
|
||||
reaction((_)=>widget.dashboardViewModel.wallet, (_) {
|
||||
_setAccountViewModel();
|
||||
setState(() {
|
||||
_lightningMode = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _setAccountViewModel() {
|
||||
accountListViewModel = widget.dashboardViewModel.balanceViewModel.hasAccounts
|
||||
? getIt.get<MoneroAccountListViewModel>()
|
||||
: null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
slivers:[
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
sliver: CupertinoSliverRefreshControl(
|
||||
refreshTriggerPullDistance: 160,
|
||||
refreshIndicatorExtent: 90,
|
||||
onRefresh: () => widget.dashboardViewModel.refreshDashboard(),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
TopBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
lightningMode: _lightningMode,
|
||||
onLightningSwitchPress: () {
|
||||
setState(() {
|
||||
_lightningMode = !_lightningMode;
|
||||
});
|
||||
},
|
||||
onSettingsButtonPress: () {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: NewSettingsPage(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
authService: getIt.get<AuthService>(),
|
||||
))),
|
||||
);
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_)=>WalletInfoBar(
|
||||
lightningMode: _lightningMode,
|
||||
hardwareWalletType: widget.dashboardViewModel.wallet.hardwareWalletType,
|
||||
name: widget.dashboardViewModel.wallet.name,
|
||||
hasCustomize: accountListViewModel != null,
|
||||
onCustomizeButtonTap: openAccountCustomizer),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => CardsView(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.name),
|
||||
onCustomizeTapped: openCardCustomizer,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
accountListViewModel: accountListViewModel,
|
||||
onCompactModeBackgroundCardsTapped: openAccountCustomizer,
|
||||
lightningMode: _lightningMode,
|
||||
),
|
||||
),
|
||||
UnconfirmedBalanceWidget(dashboardViewModel: widget.dashboardViewModel,),
|
||||
],
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
CoinActionRow(
|
||||
lightningMode: _lightningMode,
|
||||
showSwap: widget.dashboardViewModel.isEnabledSwapAction,),
|
||||
MwebAd(dashboardViewModel: widget.dashboardViewModel,),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => AssetsHistorySection(
|
||||
nftViewModel: widget.nftViewModel,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 80.0)
|
||||
],
|
||||
),
|
||||
),]
|
||||
),
|
||||
Container(
|
||||
height: (MediaQuery.of(context).padding.top),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: <Color>[
|
||||
Theme.of(context).colorScheme.surface.withAlpha(5),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(25),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(50),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(100),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(150),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(175),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(200),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void openAccountCustomizer() async {
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ModalNavigator(
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: Material(
|
||||
child: AccountCustomizer(
|
||||
accountListViewModel: accountListViewModel!,
|
||||
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>(),
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
}
|
||||
|
||||
void openCardCustomizer() async {
|
||||
final bloc = getIt.get<CardCustomizerBloc>(
|
||||
param1: _lightningMode,
|
||||
param2: widget.dashboardViewModel.settingsStore.displayAmountsInSatoshi);
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ModalNavigator(
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: BlocProvider(
|
||||
create: (context) => bloc,
|
||||
child: Material(
|
||||
child: CardCustomizer(
|
||||
cryptoTitle: widget.dashboardViewModel.wallet.currency.fullName ??
|
||||
widget.dashboardViewModel.wallet.currency.name,
|
||||
cryptoName: widget.dashboardViewModel.wallet.currency.name,
|
||||
)),
|
||||
));
|
||||
},
|
||||
);
|
||||
bloc.add(DesignSaved());
|
||||
await bloc.stream.firstWhere((s) => s is CardCustomizerSaved);
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_store.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class LightningUsernamePage extends StatefulWidget {
|
||||
LightningUsernamePage({super.key, required this.isSetup, required this.themeStore, required this.lightningUsernameBloc});
|
||||
|
||||
final bool isSetup;
|
||||
|
||||
final ThemeStore themeStore;
|
||||
late final bool isLightMode = !(themeStore.currentTheme.isDark);
|
||||
final LightningUsernameBloc lightningUsernameBloc;
|
||||
|
||||
@override
|
||||
State<LightningUsernamePage> createState() => _LightningUsernamePageState();
|
||||
}
|
||||
|
||||
class _LightningUsernamePageState extends State<LightningUsernamePage> {
|
||||
late bool _editMode;
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_editMode = widget.isSetup;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => widget.lightningUsernameBloc,
|
||||
child: BlocConsumer<LightningUsernameBloc, LightningUsernameState>(
|
||||
listener: (context, state) {
|
||||
if (state is LightningUsernameSaved) {
|
||||
if (widget.isSetup) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
if (state is LightningUsernameInitial) {
|
||||
if (!widget.isSetup) {
|
||||
_controller.text = state.username;
|
||||
}
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Material(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: "Lightning ${S.of(context).username}",
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 24,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: _editMode
|
||||
? LightningUsernameEditor(
|
||||
isSetup: widget.isSetup,
|
||||
controller: _controller,
|
||||
isLightMode: widget.isLightMode,
|
||||
onRandomizeButtonTap: () {
|
||||
randomizeUsername(context);
|
||||
},
|
||||
state: state,
|
||||
)
|
||||
: LightningUsernameInfo(
|
||||
username: state.username,
|
||||
isLightMode: widget.isLightMode,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
if (widget.isSetup) ...[
|
||||
Text(
|
||||
S.of(context).lightning_username_setup_later,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
SizedBox(),
|
||||
NewPrimaryButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
},
|
||||
text: S.of(context).skip,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.primary),
|
||||
],
|
||||
NewPrimaryButton(
|
||||
onPressed: () {
|
||||
if (_editMode) {
|
||||
context.read<LightningUsernameBloc>().add(RequestUsernameSave());
|
||||
} else {
|
||||
setState(() {
|
||||
_editMode = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
text: _editMode ? S.of(context).confirm : S.of(context).change_username,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
disabled: _editMode && (!(state is LightningUsernameReady)),
|
||||
isLoading: (state is LightningUsernameSaving),
|
||||
),
|
||||
SizedBox()
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void randomizeUsername(BuildContext context) async {
|
||||
final randomNumber = Random.secure().nextInt(9999);
|
||||
final randomName = await generateName();
|
||||
final username = "${randomName.replaceAll(" ", "")}$randomNumber".toLowerCase();
|
||||
|
||||
context.read<LightningUsernameBloc>().add(ChangeUsername(username));
|
||||
_controller.text = username;
|
||||
}
|
||||
}
|
||||
|
||||
class LightningUsernameInfo extends StatelessWidget {
|
||||
const LightningUsernameInfo({super.key, required this.username, required this.isLightMode});
|
||||
|
||||
final String username;
|
||||
final bool isLightMode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
SvgPicture.asset(isLightMode
|
||||
? "assets/new-ui/lightning_username_setup_light.svg"
|
||||
: "assets/new-ui/lightning_username_setup.svg"),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 2,
|
||||
children: [
|
||||
Text(
|
||||
username,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
usernameSuffix,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: Text(
|
||||
S.of(context).lightning_username_desc_completed,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LightningUsernameEditor extends StatelessWidget {
|
||||
const LightningUsernameEditor(
|
||||
{super.key,
|
||||
required this.controller,
|
||||
required this.onRandomizeButtonTap,
|
||||
required this.state,
|
||||
required this.isSetup,
|
||||
required this.isLightMode});
|
||||
|
||||
final TextEditingController controller;
|
||||
final VoidCallback onRandomizeButtonTap;
|
||||
final bool isLightMode;
|
||||
final bool isSetup;
|
||||
final LightningUsernameState state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
SvgPicture.asset(isLightMode
|
||||
? "assets/new-ui/lightning_username_setup_light.svg"
|
||||
: "assets/new-ui/lightning_username_setup.svg"),
|
||||
if (isSetup)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Text(
|
||||
S.of(context).lightning_username_desc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.horizontal(left: Radius.circular(16))),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
onChanged: (val) {
|
||||
context.read<LightningUsernameBloc>().add(ChangeUsername(val));
|
||||
},
|
||||
controller: controller,
|
||||
)),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onRandomizeButtonTap,
|
||||
child: SvgPicture.asset("assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn)),
|
||||
),
|
||||
),
|
||||
SizedBox()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.horizontal(right: Radius.circular(16))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
usernameSuffix,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state is LightningUsernameError)
|
||||
Text(
|
||||
(state as LightningUsernameError).error.message,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: (state as LightningUsernameError).error.isInfo
|
||||
? Colors.green
|
||||
: Theme.of(context).colorScheme.error),
|
||||
)
|
||||
else if (state is LightningUsernameReady)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: Colors.green,
|
||||
size: 12,
|
||||
),
|
||||
Text(
|
||||
S.of(context).username_available,
|
||||
style: TextStyle(color: Colors.green),
|
||||
)
|
||||
],
|
||||
)
|
||||
else
|
||||
Text(""),
|
||||
SizedBox(height: MediaQuery.of(context).viewInsets.bottom.clamp(0, 100))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
import 'package:cake_wallet/core/utilities.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
|
||||
class NewReceivePage extends StatefulWidget {
|
||||
NewReceivePage(
|
||||
{super.key,
|
||||
required this.addressListViewModel,
|
||||
required this.receiveOptionViewModel,
|
||||
required this.dashboardViewModel,
|
||||
required this.lightningMode,
|
||||
CryptoCurrency? initialCurrency}) {
|
||||
if (initialCurrency != null && initialCurrency != addressListViewModel.selectedCurrency) {
|
||||
addressListViewModel.setTokenCurrency(initialCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final ReceiveOptionViewModel receiveOptionViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final bool lightningMode;
|
||||
|
||||
@override
|
||||
State<NewReceivePage> createState() => _NewReceivePageState();
|
||||
}
|
||||
|
||||
class _NewReceivePageState extends State<NewReceivePage> {
|
||||
bool _largeQrMode = false;
|
||||
late WalletAddressListItem? _addressItemWithLabel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.lightningMode) {
|
||||
widget.receiveOptionViewModel.selectReceiveOption(
|
||||
widget.receiveOptionViewModel.options
|
||||
.firstWhereOrNull((item) => item.value.contains("Lightning")) ??
|
||||
ReceivePageOption.mainnet,
|
||||
);
|
||||
widget.addressListViewModel.setTokenCurrency(CryptoCurrency.btcln);
|
||||
} else if (widget.addressListViewModel.wallet.type == WalletType.bitcoin) {
|
||||
widget.receiveOptionViewModel.selectReceiveOption(
|
||||
widget.receiveOptionViewModel.options
|
||||
.firstWhereOrNull((item) => item.value.contains("Standard")) ??
|
||||
ReceivePageOption.mainnet,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => widget.addressListViewModel.uri, (newAddress) {
|
||||
_reloadAddressWithLabel(newAddress);
|
||||
});
|
||||
|
||||
_addressItemWithLabel =
|
||||
widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull((item) {
|
||||
return (item is WalletAddressListItem &&
|
||||
item.address == widget.addressListViewModel.uri.address);
|
||||
}) as WalletAddressListItem?;
|
||||
|
||||
reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption,
|
||||
(ReceivePageOption option) {
|
||||
if (widget.dashboardViewModel.type == WalletType.bitcoin &&
|
||||
bitcoin!.isBitcoinReceivePageOption(option)) {
|
||||
widget.addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
|
||||
return;
|
||||
}
|
||||
if (widget.dashboardViewModel.type == WalletType.zcash) {
|
||||
widget.addressListViewModel.setAddressType(zcash!.getOptionToType(option));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case ReceivePageOption.anonPayInvoice:
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.anonPayInvoicePage,
|
||||
arguments: [widget.addressListViewModel.address.address, option],
|
||||
);
|
||||
break;
|
||||
case ReceivePageOption.anonPayDonationLink:
|
||||
final sharedPreferences = getIt.get<SharedPreferences>();
|
||||
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
|
||||
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
|
||||
final donationWalletName =
|
||||
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
|
||||
|
||||
if (clearnetUrl != null &&
|
||||
onionUrl != null &&
|
||||
widget.addressListViewModel.wallet.name == donationWalletName) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.anonPayReceivePage,
|
||||
arguments: AnonPayReceivePageArgs(
|
||||
invoiceInfo: AnonpayDonationLinkInfo(
|
||||
clearnetUrl: clearnetUrl,
|
||||
onionUrl: onionUrl,
|
||||
address: widget.addressListViewModel.address.address,
|
||||
),
|
||||
qrImage: widget.addressListViewModel.qrImage,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.anonPayInvoicePage,
|
||||
arguments: [widget.addressListViewModel.address.address, option],
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ([WalletType.bitcoin, WalletType.litecoin]
|
||||
.contains(widget.addressListViewModel.type)) {
|
||||
widget.addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
|
||||
}
|
||||
if (widget.addressListViewModel.type == WalletType.zcash) {
|
||||
printV("help me i'll kms if that wont work: ${zcash!.getZcashAddressType(option)}");
|
||||
widget.addressListViewModel.setAddressType(zcash!.getZcashAddressType(option));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasAddressTypeSelector = widget.receiveOptionViewModel.options.length > 1;
|
||||
final hasLabel = _addressItemWithLabel?.name != null && _addressItemWithLabel!.name!.isNotEmpty;
|
||||
final infoboxDismissed = widget.addressListViewModel.wallet.walletInfo.receiveInfoboxDismissed;
|
||||
final infobox = ReceiveInfoBox.forWalletType(widget.addressListViewModel.type,
|
||||
onDismissed: () {
|
||||
widget.addressListViewModel.dismissInfobox();
|
||||
setState(() {});
|
||||
}, autoGenerateSubaddressStatus: widget.lightningMode ? AutoGenerateSubaddressStatus.disabled : widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surfaceBright,
|
||||
Theme.of(context).colorScheme.surface,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: _largeQrMode ? "" : S.of(context).receive,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
trailingIcon: _largeQrMode
|
||||
? Icon(Icons.share)
|
||||
: widget.addressListViewModel.hasAddressRotation
|
||||
/* TODO rotating is broken on mweb, disabling for now, fix after mvp*/
|
||||
&&
|
||||
!(widget.receiveOptionViewModel.selectedReceiveOption.description ?? "")
|
||||
.toLowerCase()
|
||||
.contains("mweb")
|
||||
? Icon(Icons.refresh)
|
||||
: null,
|
||||
onLeadingPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
onTrailingPressed: () {
|
||||
if (_largeQrMode) {
|
||||
ShareUtil.share(
|
||||
text: widget.addressListViewModel.uri.toString(),
|
||||
context: context,
|
||||
);
|
||||
} else if (widget.addressListViewModel.hasAddressRotation) {
|
||||
widget.addressListViewModel.rotateAddress();
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
ReceiveAmountDisplay(
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
largeQrMode: _largeQrMode,
|
||||
),
|
||||
ReceiveQrCode(
|
||||
addressListViewModel: widget.addressListViewModel,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_largeQrMode = !_largeQrMode;
|
||||
// _infoboxDimissed = true;
|
||||
widget.addressListViewModel.dismissInfobox();
|
||||
});
|
||||
},
|
||||
largeQrMode: _largeQrMode,
|
||||
),
|
||||
if (widget.addressListViewModel.tokenCurrency != null)
|
||||
ReceiveTokenDisplay(addressListViewModel: widget.addressListViewModel),
|
||||
if (hasAddressTypeSelector)
|
||||
ReceiveAddressTypeDisplay(
|
||||
lightningMode: widget.lightningMode,
|
||||
receiveOptionViewModel: widget.receiveOptionViewModel,
|
||||
largeQrMode: _largeQrMode,
|
||||
),
|
||||
ReceiveAddressWidget(
|
||||
addressListViewModel: widget.addressListViewModel,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _showLabelModal,
|
||||
child: ReceiveLabelWidget(
|
||||
name: _addressItemWithLabel?.name ?? "",
|
||||
largeQrMode: _largeQrMode,
|
||||
)),
|
||||
Observer(
|
||||
builder: (_) => ReceiveBottomButtons(
|
||||
key: const ValueKey(0),
|
||||
largeQrMode: _largeQrMode,
|
||||
showAccountsButton: widget.addressListViewModel.hasAddressList,
|
||||
showLabelButton: widget.addressListViewModel.hasAddressList && !hasLabel,
|
||||
onCopyButtonPressed: () {
|
||||
if (widget.addressListViewModel.hasPayjoin) {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
PayjoinCopyModal(uri: widget.addressListViewModel.uri));
|
||||
} else {
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: widget.addressListViewModel.displayAmount.isEmpty
|
||||
? widget.addressListViewModel.uri.address
|
||||
: widget.addressListViewModel.uri.toString()),
|
||||
);
|
||||
}
|
||||
},
|
||||
onAmountButtonPressed: () {
|
||||
showMaterialModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: Colors.black.withAlpha(80),
|
||||
builder: (context) {
|
||||
return ReceiveAmountModal(
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
onSubmitted: (amount) {},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onLabelButtonPressed: _showLabelModal,
|
||||
onAccountsButtonPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.receiveAddresses,
|
||||
arguments: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ReceiveLargeAmountPreview(
|
||||
amount: widget.addressListViewModel.displayAmount,
|
||||
currency: widget.addressListViewModel.cryptoCurrencySymbol,
|
||||
largeQrMode: _largeQrMode),
|
||||
if (infobox != null && !widget.addressListViewModel.isLightning)
|
||||
ClipRect(
|
||||
child: AnimatedAlign(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutCubic,
|
||||
heightFactor: infoboxDismissed ? 0 : 1,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
opacity: infoboxDismissed ? 0 : 1,
|
||||
curve: Curves.easeOutCubic,
|
||||
child: infobox),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLabelModal() {
|
||||
showMaterialModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: Colors.black.withAlpha(80),
|
||||
builder: (context) {
|
||||
return getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel);
|
||||
}).then((value) {
|
||||
_reloadAddressWithLabel(widget.addressListViewModel.uri);
|
||||
});
|
||||
}
|
||||
|
||||
void _reloadAddressWithLabel(PaymentURI newAddress) {
|
||||
// FIXME: viewmodel doesn't want to load address name here, so we make it. investigate why later
|
||||
setState(() {
|
||||
_addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull(
|
||||
(item) => (item is WalletAddressListItem && item.address == newAddress.address))
|
||||
as WalletAddressListItem;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ScanPage extends StatelessWidget {
|
||||
const ScanPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,147 @@
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
bool _trueFunc(DashboardViewModel _) => true;
|
||||
|
||||
bool _falseFunc(DashboardViewModel _) => false;
|
||||
|
||||
bool _isBtc(DashboardViewModel vm) => vm.wallet.type == WalletType.bitcoin;
|
||||
|
||||
bool _hasMweb(DashboardViewModel vm) => vm.hasMweb;
|
||||
|
||||
bool _isCupcake(DashboardViewModel vm) => vm.wallet.hardwareWalletType == HardwareWalletType.cupcake;
|
||||
|
||||
class SettingsListItem {
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final String route;
|
||||
final Object? routeArgs;
|
||||
final bool requireAuth;
|
||||
final bool Function(DashboardViewModel) use2fa;
|
||||
final bool Function(DashboardViewModel) condition;
|
||||
|
||||
const SettingsListItem(this.iconPath, this.title, this.route, {this.routeArgs = null, this.requireAuth = false, this.use2fa = _falseFunc, this.condition = _trueFunc});
|
||||
}
|
||||
|
||||
class SettingsSectionData {
|
||||
final String title;
|
||||
final String titleIconPath;
|
||||
final List<SettingsListItem> items;
|
||||
|
||||
const SettingsSectionData(this.title, this.titleIconPath, this.items);
|
||||
|
||||
static SettingsSectionData walletSettings =
|
||||
SettingsSectionData(S.current.wallet_settings, "assets/new-ui/wallet_settings.svg", [
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/nodes.svg", S.current.nodes, Routes.manageNodes),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/privacy.svg", S.current.privacy, Routes.privacyPage),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/seed.svg", S.current.seed_and_keys, Routes.showKeys,
|
||||
routeArgs: true, requireAuth: true, use2fa: (vm)=>vm.settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/lightning_username.svg",
|
||||
"Lightning ${S.current.username}", Routes.lightningUsernamePage, condition: _isBtc),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/silent-payments.svg", S.current.silent_payments_settings, Routes.silentPaymentsSettings, condition: _isBtc),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/mweb.svg", S.current.litecoin_mweb_settings, Routes.mwebSettings, condition: _hasMweb),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/cupcake.svg", S.current.export_outputs, Routes.urqrAnimatedPage, routeArgs: {'export-outputs': 'export-outputs'}, condition: _isCupcake),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/other.svg", S.current.other, Routes.otherSettingsPage),
|
||||
]);
|
||||
|
||||
static SettingsSectionData appSettings =
|
||||
SettingsSectionData(S.current.app_settings, "assets/new-ui/app_settings.svg", [
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/connections.svg", S.current.connections, Routes.connectionSync),
|
||||
// SettingsListItem("assets/new-ui/settings_row_icons/defaults.svg", "Defaults", ""),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/display.svg", S.current.display, Routes.displaySettingsPage),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/security.svg", S.current.security, Routes.securityBackupPage),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/backup.svg", S.current.backup, Routes.backup, requireAuth: true, use2fa: (vm)=>vm.settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
|
||||
]);
|
||||
|
||||
static SettingsSectionData otherSettings = SettingsSectionData("", "", [
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/support.svg", S.current.settings_support, Routes.support),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/info.svg", S.current.about, Routes.aboutPage),
|
||||
]);
|
||||
|
||||
static List<SettingsSectionData> all = [walletSettings, appSettings, otherSettings];
|
||||
}
|
||||
|
||||
class NewSettingsPage extends StatelessWidget {
|
||||
const NewSettingsPage({super.key, required this.dashboardViewModel, required this.authService});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final AuthService authService;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ModalNavigator(
|
||||
parentContext: context,
|
||||
rootPage: SettingsMainPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
authService: authService,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsMainPage extends StatelessWidget {
|
||||
const SettingsMainPage({super.key, required this.dashboardViewModel, required this.authService});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final AuthService authService;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Map<String, List<ListItem>> sections =
|
||||
Map.fromEntries(SettingsSectionData.all.map((section) => MapEntry(
|
||||
section.title,
|
||||
section.items
|
||||
.map((item) => item.condition(dashboardViewModel)
|
||||
? ListItemRegularRow(
|
||||
keyValue: item.title,
|
||||
label: item.title,
|
||||
iconPath: item.iconPath,
|
||||
onTap: () {
|
||||
if (item.route.isNotEmpty) {
|
||||
if(item.requireAuth) {
|
||||
authService.authenticateAction(context,
|
||||
conditionToDetermineIfToUse2FA: item.use2fa(dashboardViewModel),
|
||||
route: item.route);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(item.route, arguments: item.routeArgs);
|
||||
}
|
||||
}
|
||||
})
|
||||
: null)
|
||||
.whereType<ListItem>()
|
||||
.toList())));
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(children: [
|
||||
ModalTopBar(
|
||||
title: "Settings",
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
|
||||
onTrailingPressed: () {},
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: ModalScrollController.of(context),
|
||||
children: [Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
|
||||
child: NewListSections(
|
||||
sections: sections,
|
||||
),
|
||||
),]
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import "package:cw_core/balance_card_style_settings.dart";
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import 'package:flutter/src/painting/gradient.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
part 'card_customizer_event.dart';
|
||||
part 'card_customizer_state.dart';
|
||||
|
||||
class CardCustomizerBloc extends Bloc<CardCustomizerEvent, CardCustomizerState> {
|
||||
final WalletBase _wallet;
|
||||
final bool lightningMode;
|
||||
final bool displaySats;
|
||||
|
||||
CardCustomizerBloc(this._wallet, {this.lightningMode = false, this.displaySats = false})
|
||||
: super(CardCustomizerNotLoaded(0, 0, [CardDesign.genericDefault], [], "", -1, displaySats, 0)) {
|
||||
|
||||
on<_Init>(_init);
|
||||
on<CardDesignSelected>(_onDesignSelected);
|
||||
on<ColorSelected>(_onColorSelected);
|
||||
on<DesignSaved>(_onDesignSaved);
|
||||
on<AccountNameChanged>(_onAccountNameChanged);
|
||||
|
||||
|
||||
add(_Init());
|
||||
}
|
||||
|
||||
List<Gradient> _updateAvailableColors(CardDesign currentDesign) {
|
||||
final list = List<Gradient>.from(CardDesign.allGradients, growable: true);
|
||||
if (CardDesign.specialDesignsForCurrencies[_wallet.currency] != null) {
|
||||
list.add(CardDesign.specialDesignsForCurrencies[_wallet.currency]!.gradient);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<BalanceCardStyleSettings?> _loadCurrentDesignSettings(int accountIndex) async {
|
||||
return (await BalanceCardStyleSettings.get(_wallet.walletInfo.internalId, accountIndex));
|
||||
}
|
||||
|
||||
List<CardDesign> _initAvailableDesigns({bool lightningMode = false}) {
|
||||
final List<CardDesign> ret = List<CardDesign>.empty(growable: true);
|
||||
final curr = lightningMode ? CryptoCurrency.btcln : _wallet.currency;
|
||||
|
||||
ret.add(CardDesign.forCurrencyIcon(curr));
|
||||
|
||||
if (CardDesign.specialDesignsForCurrencies[curr] != null)
|
||||
ret.add(CardDesign.forCurrencySpecial(curr));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int _initSelectedDesign(CardDesign currentDesign) {
|
||||
if (currentDesign.backgroundType == CardDesignBackgroundTypes.svgIcon)
|
||||
return 0;
|
||||
else if (currentDesign.backgroundType == CardDesignBackgroundTypes.svgFull)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _initSelectedColor(CardDesign currentDesign) {
|
||||
int ret = CardDesign.allGradients.indexOf(currentDesign.gradient);
|
||||
if(ret == -1) {
|
||||
// special design with its own color. select last color in list.
|
||||
return CardDesign.allGradients.length;
|
||||
} else if (ret == -1) {
|
||||
// no color selected, select default.
|
||||
return 0;
|
||||
} else {
|
||||
// select whatever's selected.
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void _init(_Init event, Emitter<CardCustomizerState> emit) async {
|
||||
late final account;
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
account = monero!.getCurrentAccount(_wallet);
|
||||
} else if (_wallet.type == WalletType.wownero) {
|
||||
account = wownero!.getCurrentAccount(_wallet);
|
||||
} else {
|
||||
account = null;
|
||||
}
|
||||
final accountName = (account?.label ?? "") as String;
|
||||
late final int accountIndex;
|
||||
if(account != null) {
|
||||
accountIndex = account.id as int;
|
||||
} else if(lightningMode) {
|
||||
accountIndex = 0;
|
||||
} else {
|
||||
accountIndex = -1;
|
||||
}
|
||||
final currentDesignSettings = await _loadCurrentDesignSettings(accountIndex);
|
||||
final currentDesign = CardDesign.fromStyleSettings(currentDesignSettings, lightningMode ? CryptoCurrency.btcln : _wallet.currency);
|
||||
final availableDesigns = _initAvailableDesigns(lightningMode: lightningMode);
|
||||
final availableColors = _updateAvailableColors(currentDesign);
|
||||
final selectedDesign = _initSelectedDesign(currentDesign);
|
||||
final selectedColor = _initSelectedColor(currentDesign);
|
||||
|
||||
emit(CardCustomizerInitial(selectedDesign, selectedColor, availableDesigns, availableColors,
|
||||
accountName, accountIndex, displaySats, currentDesignSettings?.cardOrder ?? 0));
|
||||
}
|
||||
|
||||
void _onDesignSelected(CardDesignSelected event, Emitter<CardCustomizerState> emit) {
|
||||
final newColors = _updateAvailableColors(state.availableDesigns[event.newDesignIndex]);
|
||||
late final int newColorIndex;
|
||||
if (newColors.length < state.availableColors.length) {
|
||||
newColorIndex = 0;
|
||||
} else {
|
||||
newColorIndex = state.selectedColorIndex;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
selectedDesignIndex: event.newDesignIndex,
|
||||
availableColors: newColors,
|
||||
selectedColorIndex: newColorIndex));
|
||||
}
|
||||
|
||||
void _onColorSelected(ColorSelected event, Emitter<CardCustomizerState> emit) {
|
||||
emit(state.copyWith(selectedColorIndex: event.newColorIndex));
|
||||
}
|
||||
|
||||
void _onAccountNameChanged(AccountNameChanged event, Emitter<CardCustomizerState> emit) {
|
||||
emit(state.copyWith(accountName: event.newAccountName));
|
||||
}
|
||||
|
||||
void _onDesignSaved(DesignSaved event, Emitter<CardCustomizerState> emit) {
|
||||
BalanceCardStyleSettings.fromCardDesign(
|
||||
_wallet.walletInfo.internalId, state.accountIndex, state.cardOrder, state.selectedDesign)
|
||||
.insert()
|
||||
.then((value) {
|
||||
emit(CardCustomizerSaved(state.selectedDesignIndex, state.selectedColorIndex,
|
||||
state.availableDesigns, state.availableColors, state.accountName, state.accountIndex, state.displaySats, state.cardOrder));
|
||||
});
|
||||
saveAccountName();
|
||||
}
|
||||
|
||||
Future<void> saveAccountName() async {
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
await saveMoneroAccountName();
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.wownero) {
|
||||
await saveWowneroAccountName();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveMoneroAccountName() async {
|
||||
final MoneroAccountList moneroAccountList = monero!.getAccountList(_wallet);
|
||||
await moneroAccountList.setLabelAccount(_wallet,
|
||||
accountIndex: state.accountIndex, label: state.accountName);
|
||||
|
||||
await _wallet.save();
|
||||
}
|
||||
|
||||
Future<void> saveWowneroAccountName() async {
|
||||
final WowneroAccountList wowneroAccountList = wownero!.getAccountList(_wallet);
|
||||
await wowneroAccountList.setLabelAccount(_wallet,
|
||||
accountIndex: state.accountIndex, label: state.accountName);
|
||||
|
||||
await _wallet.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
part of 'card_customizer_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class CardCustomizerEvent {}
|
||||
|
||||
class _Init extends CardCustomizerEvent {}
|
||||
|
||||
class CardDesignSelected extends CardCustomizerEvent {
|
||||
final int newDesignIndex;
|
||||
|
||||
CardDesignSelected(this.newDesignIndex);
|
||||
}
|
||||
|
||||
class ColorSelected extends CardCustomizerEvent {
|
||||
final int newColorIndex;
|
||||
|
||||
ColorSelected(this.newColorIndex);
|
||||
}
|
||||
|
||||
|
||||
class AccountNameChanged extends CardCustomizerEvent {
|
||||
final String newAccountName;
|
||||
|
||||
AccountNameChanged(this.newAccountName);
|
||||
}
|
||||
|
||||
|
||||
class DesignSaved extends CardCustomizerEvent {}
|
||||
@@ -0,0 +1,112 @@
|
||||
part of 'card_customizer_bloc.dart';
|
||||
|
||||
sealed class CardCustomizerState {
|
||||
final int selectedDesignIndex;
|
||||
final int selectedColorIndex;
|
||||
final String accountName;
|
||||
final int accountIndex;
|
||||
final int cardOrder;
|
||||
final bool displaySats;
|
||||
final List<CardDesign> availableDesigns;
|
||||
final List<Gradient> availableColors;
|
||||
|
||||
CardCustomizerState(
|
||||
this.selectedDesignIndex,
|
||||
this.selectedColorIndex,
|
||||
this.availableDesigns,
|
||||
this.availableColors,
|
||||
this.accountName,
|
||||
this.accountIndex,
|
||||
this.displaySats,
|
||||
this.cardOrder
|
||||
);
|
||||
|
||||
CardDesign get selectedDesign {
|
||||
return availableDesigns[selectedDesignIndex].withGradient(selectedColor);
|
||||
}
|
||||
|
||||
CardCustomizerState copyWith({
|
||||
int? selectedDesignIndex,
|
||||
int? selectedColorIndex,
|
||||
List<CardDesign>? availableDesigns,
|
||||
List<Gradient>? availableColors,
|
||||
String? accountName,
|
||||
int? accountIndex,
|
||||
int? cardOrder,
|
||||
});
|
||||
|
||||
Gradient get selectedColor => availableColors[selectedColorIndex];
|
||||
}
|
||||
|
||||
final class CardCustomizerNotLoaded extends CardCustomizerState {
|
||||
CardCustomizerNotLoaded(super.selectedDesignIndex, super.selectedColorIndex, super.availableDesigns, super.availableColors, super.accountName, super.accountIndex, super.displaySats, super.cardOrder);
|
||||
|
||||
@override
|
||||
CardCustomizerState copyWith({int? selectedDesignIndex, int? selectedColorIndex, List<CardDesign>? availableDesigns, List<Gradient>? availableColors, String? accountName, int? accountIndex, int? cardOrder}) {
|
||||
// this is never gonna be copied. it's near-instantly replaced with initial
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
final class CardCustomizerInitial extends CardCustomizerState {
|
||||
CardCustomizerInitial(
|
||||
int selectedDesignIndex,
|
||||
int selectedColorIndex,
|
||||
List<CardDesign> availableDesigns,
|
||||
List<Gradient> availableColors,
|
||||
String accountName,
|
||||
int accountIndex,
|
||||
bool displaySats,
|
||||
int cardOrder,
|
||||
) : super(selectedDesignIndex, selectedColorIndex, availableDesigns, availableColors, accountName,
|
||||
accountIndex, displaySats, cardOrder);
|
||||
|
||||
CardCustomizerInitial copyWith({
|
||||
int? selectedDesignIndex,
|
||||
int? selectedColorIndex,
|
||||
List<CardDesign>? availableDesigns,
|
||||
List<Gradient>? availableColors,
|
||||
String? accountName,
|
||||
int? accountIndex,
|
||||
bool? displaySats,
|
||||
int? cardOrder,
|
||||
}) {
|
||||
return CardCustomizerInitial(
|
||||
selectedDesignIndex ?? this.selectedDesignIndex,
|
||||
selectedColorIndex ?? this.selectedColorIndex,
|
||||
availableDesigns ?? this.availableDesigns,
|
||||
availableColors ?? this.availableColors,
|
||||
accountName ?? this.accountName,
|
||||
accountIndex ?? this.accountIndex,
|
||||
displaySats ?? this.displaySats,
|
||||
cardOrder ?? this.cardOrder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class CardCustomizerSaved extends CardCustomizerState {
|
||||
CardCustomizerSaved(super.selectedDesignIndex, super.selectedColorIndex, super.availableDesigns,
|
||||
super.availableColors, super.accountName, super.accountIndex, super.displaySats, super.cardOrder);
|
||||
|
||||
@override
|
||||
CardCustomizerState copyWith(
|
||||
{int? selectedDesignIndex,
|
||||
int? selectedColorIndex,
|
||||
List<CardDesign>? availableDesigns,
|
||||
List<Gradient>? availableColors,
|
||||
String? accountName,
|
||||
int? accountIndex,
|
||||
bool? displaySats,
|
||||
int? cardOrder}) {
|
||||
return CardCustomizerSaved(
|
||||
selectedDesignIndex ?? this.selectedDesignIndex,
|
||||
selectedColorIndex ?? this.selectedColorIndex,
|
||||
availableDesigns ?? this.availableDesigns,
|
||||
availableColors ?? this.availableColors,
|
||||
accountName ?? this.accountName,
|
||||
accountIndex ?? this.accountIndex,
|
||||
displaySats ?? this.displaySats,
|
||||
cardOrder ?? this.cardOrder,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/lnurlpay_record.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
part 'lightning_username_event.dart';
|
||||
|
||||
part 'lightning_username_state.dart';
|
||||
|
||||
class UsernameError {
|
||||
String message;
|
||||
bool isInfo;
|
||||
|
||||
UsernameError(this.message, {this.isInfo = false});
|
||||
}
|
||||
|
||||
// TODO find other errors that api returns
|
||||
final Map<String, String> errorPatterns = {"name already taken": S.current.username_not_available};
|
||||
|
||||
const usernameRegex = r"^[a-z0-9_.-]+$";
|
||||
const usernameSuffix = "@cake.cash";
|
||||
|
||||
class LightningUsernameBloc extends Bloc<LightningUsernameEvent, LightningUsernameState> {
|
||||
final WalletBase _wallet;
|
||||
|
||||
LightningUsernameBloc(this._wallet) : super(LightningUsernameInitial("")) {
|
||||
on<ChangeUsername>(onUsernameChanged, transformer: restartable());
|
||||
|
||||
on<RequestUsernameSave>(onUsernameSaveRequested);
|
||||
|
||||
on<_Init>((event, emit) async {
|
||||
final username = await bitcoin!.getLightningUsername(_wallet);
|
||||
emit(LightningUsernameInitial(username ?? ""));
|
||||
});
|
||||
|
||||
add(_Init());
|
||||
}
|
||||
|
||||
Future<void> onUsernameChanged(ChangeUsername event, Emitter<LightningUsernameState> emit) async {
|
||||
emit(LightningUsernameChecking(event.newUsername));
|
||||
|
||||
final error = await getUsernameError(event.newUsername);
|
||||
|
||||
if (error != null) {
|
||||
emit(LightningUsernameError(event.newUsername, error));
|
||||
} else {
|
||||
emit(LightningUsernameReady(event.newUsername));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onUsernameSaveRequested(RequestUsernameSave event, Emitter<LightningUsernameState> emit) async {
|
||||
emit(LightningUsernameSaving(state.username));
|
||||
try {
|
||||
await bitcoin!.setLightningUsername(_wallet, state.username);
|
||||
emit(LightningUsernameSaved(state.username));
|
||||
} catch (e) {
|
||||
for (final pattern in errorPatterns.keys) {
|
||||
if (e.toString().contains(pattern)) {
|
||||
emit(LightningUsernameError(state.username, UsernameError(errorPatterns[pattern]!)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit(LightningUsernameError(state.username, UsernameError(bitcoin!.getBreezSdkError(e) ?? e.toString())));
|
||||
}
|
||||
}
|
||||
|
||||
Future<UsernameError?> getUsernameError(String username) async {
|
||||
if (!(RegExp(usernameRegex).hasMatch(username))) {
|
||||
return UsernameError("${S.current.username_character_error} a-z, 0-9, - _ .");
|
||||
}
|
||||
|
||||
if (username.length < 1 || username.length > 64) {
|
||||
return UsernameError(S.current.username_length_error_1_64);
|
||||
}
|
||||
|
||||
if (username == (await bitcoin!.getLightningUsername(_wallet))) {
|
||||
return UsernameError(S.current.already_your_username, isInfo: true);
|
||||
}
|
||||
|
||||
if ((await LNUrlPayRecord.checkWellKnownUsername(
|
||||
"${username}${usernameSuffix}", CryptoCurrency.btc)) !=
|
||||
null) {
|
||||
return UsernameError(S.current.username_not_available);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
part of 'lightning_username_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class LightningUsernameEvent {}
|
||||
|
||||
final class _Init extends LightningUsernameEvent {}
|
||||
|
||||
final class ChangeUsername extends LightningUsernameEvent {
|
||||
final String newUsername;
|
||||
|
||||
ChangeUsername(this.newUsername);
|
||||
}
|
||||
|
||||
final class RequestUsernameSave extends LightningUsernameEvent {}
|
||||
@@ -0,0 +1,34 @@
|
||||
part of 'lightning_username_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class LightningUsernameState {
|
||||
final String username;
|
||||
|
||||
LightningUsernameState(this.username);
|
||||
}
|
||||
|
||||
final class LightningUsernameInitial extends LightningUsernameState {
|
||||
LightningUsernameInitial(super.username);
|
||||
}
|
||||
|
||||
final class LightningUsernameChecking extends LightningUsernameState {
|
||||
LightningUsernameChecking(super.username);
|
||||
}
|
||||
|
||||
final class LightningUsernameError extends LightningUsernameState {
|
||||
final UsernameError error;
|
||||
|
||||
LightningUsernameError(super.username, this.error);
|
||||
}
|
||||
|
||||
final class LightningUsernameReady extends LightningUsernameState {
|
||||
LightningUsernameReady(super.username);
|
||||
}
|
||||
|
||||
final class LightningUsernameSaving extends LightningUsernameState {
|
||||
LightningUsernameSaving(super.username);
|
||||
}
|
||||
|
||||
final class LightningUsernameSaved extends LightningUsernameState {
|
||||
LightningUsernameSaved(super.username);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_text_field.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class AddressLabelInputPopup extends StatefulWidget {
|
||||
const AddressLabelInputPopup({
|
||||
super.key,
|
||||
required this.walletAddressEditOrCreateViewModel,
|
||||
});
|
||||
|
||||
final WalletAddressEditOrCreateViewModel walletAddressEditOrCreateViewModel;
|
||||
|
||||
@override
|
||||
State<AddressLabelInputPopup> createState() => _AddressLabelInputPopupState();
|
||||
}
|
||||
|
||||
class _AddressLabelInputPopupState extends State<AddressLabelInputPopup> {
|
||||
late final TextEditingController _controller;
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController();
|
||||
_focusNode = FocusNode();
|
||||
|
||||
reaction((_) => widget.walletAddressEditOrCreateViewModel.state,
|
||||
(AddressEditOrCreateState state) {
|
||||
if (state is AddressSavedSuccessfully) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted)
|
||||
Navigator.of(context).pop(widget.walletAddressEditOrCreateViewModel.label);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_focusNode.requestFocus();
|
||||
_controller.text = widget.walletAddressEditOrCreateViewModel.label;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: NewListSections(
|
||||
sections: {
|
||||
"": [
|
||||
ListItemTextField(
|
||||
keyValue: "label",
|
||||
label: S.of(context).label,
|
||||
focusNode: _focusNode,
|
||||
onFieldSubmitted: (value) {
|
||||
widget.walletAddressEditOrCreateViewModel.label = value;
|
||||
widget.walletAddressEditOrCreateViewModel.save();
|
||||
// Navigator.of(context).pop();
|
||||
})
|
||||
]
|
||||
},
|
||||
controllers: {"label": _controller},
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:cake_wallet/new-ui/widgets/dropdown_row.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedDropdown extends StatefulWidget {
|
||||
const AnimatedDropdown({super.key,required this.content, required this.dropdownText});
|
||||
|
||||
final Widget content;
|
||||
final String dropdownText;
|
||||
|
||||
@override
|
||||
State<AnimatedDropdown> createState() => _AnimatedDropdownState();
|
||||
}
|
||||
|
||||
class _AnimatedDropdownState extends State<AnimatedDropdown> {
|
||||
bool _expanded = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRSuperellipse(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,),
|
||||
child: Column(
|
||||
children: [
|
||||
DropdownRow(expanded: _expanded, text: widget.dropdownText,onTap: (){
|
||||
setState(() {
|
||||
_expanded = !_expanded;
|
||||
});
|
||||
},),
|
||||
AnimatedOpacity(duration:Duration(milliseconds: 150),opacity:_expanded?1:0,child: Divider(height: 1,thickness: 1,)),
|
||||
AnimatedCrossFade(
|
||||
firstChild: Container(height:0),
|
||||
secondChild: widget.content,
|
||||
crossFadeState: _expanded
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
sizeCurve: Curves.easeOutCubic,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class AppsWidget extends StatelessWidget {
|
||||
AppsWidget({
|
||||
this.onTap,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
this.hint,
|
||||
this.svgPicture,
|
||||
this.image,
|
||||
this.icon,
|
||||
this.onClose,
|
||||
this.customBorder,
|
||||
this.shadowSpread,
|
||||
this.shadowBlur,
|
||||
super.key,
|
||||
this.marginV,
|
||||
this.marginH,
|
||||
this.isWide,
|
||||
this.isLink,
|
||||
this.isCake,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onClose;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final Widget? hint;
|
||||
final SvgPicture? svgPicture;
|
||||
final Widget? icon;
|
||||
final String? image;
|
||||
final double? customBorder;
|
||||
final double? marginV;
|
||||
final double? marginH;
|
||||
final double? shadowSpread;
|
||||
final double? shadowBlur;
|
||||
final bool? isWide;
|
||||
final bool? isLink;
|
||||
final bool? isCake;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isWide == true) {
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: marginH ?? 20, vertical: marginV ?? 5),
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.customColors.cardGradientColorPrimary,
|
||||
context.customColors.cardGradientColorSecondary,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: TextButton(
|
||||
onPressed: onTap,
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
side: BorderSide(
|
||||
width: 1.25, color: Theme.of(context).colorScheme.surfaceContainerHigh),
|
||||
),
|
||||
padding: EdgeInsets.all(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: CakeImageWidget(imageUrl: image, height: 54, width: 54),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 20,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
isCake == true ? CakeImageWidget(imageUrl: "assets/new-ui/cakelabs-icon.svg", color: Theme.of(context).colorScheme.onSurfaceVariant) : SizedBox(),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
isLink == true ? Icons.arrow_outward : Icons.arrow_forward_ios,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
size: 20,
|
||||
)
|
||||
|
||||
],
|
||||
),
|
||||
if (hint != null) ...[
|
||||
SizedBox(height: 10),
|
||||
hint!,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (onClose != null)
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: onClose,
|
||||
//color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Stack(
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const changelogIconLocation = "assets/new-ui/changelog/icons";
|
||||
const changelogTextLocation = "assets/new-ui/changelog/text";
|
||||
|
||||
class ChangelogItem {
|
||||
final String iconFilename;
|
||||
final String title;
|
||||
final String description;
|
||||
|
||||
ChangelogItem(this.iconFilename, this.title, this.description);
|
||||
}
|
||||
|
||||
class ChangelogModal extends StatefulWidget {
|
||||
const ChangelogModal({super.key, required this.version});
|
||||
|
||||
final String version;
|
||||
|
||||
@override
|
||||
State<ChangelogModal> createState() => _ChangelogModalState();
|
||||
}
|
||||
|
||||
class _ChangelogModalState extends State<ChangelogModal> {
|
||||
List<ChangelogItem> _items = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadChangelog();
|
||||
}
|
||||
|
||||
void loadChangelog() async {
|
||||
String lang = WidgetsBinding.instance.platformDispatcher.locale.languageCode;
|
||||
if (lang == "und" ||
|
||||
lang.isEmpty ||
|
||||
!(await File("$changelogTextLocation/changelog_$lang.json").exists())) {
|
||||
lang = "en";
|
||||
}
|
||||
|
||||
final List<dynamic> changelog =
|
||||
jsonDecode(await rootBundle.loadString("$changelogTextLocation/changelog_$lang.json"))
|
||||
as List<dynamic>;
|
||||
|
||||
for (final item in changelog) {
|
||||
final itemMap = item as Map<String, dynamic>;
|
||||
_items.add(ChangelogItem(
|
||||
itemMap["icon"] as String? ?? "",
|
||||
itemMap["title"] as String? ?? "",
|
||||
itemMap["description"] as String? ?? "",
|
||||
));
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_items.isEmpty) return SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
SizedBox(),
|
||||
ModalTopBar(title: S.of(context).whats_new),
|
||||
SizedBox(),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
VersionNumberHeader(version: widget.version),
|
||||
SizedBox(height: 40),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 32,
|
||||
children: _items.map((item) => ChangelogItemWidget(item: item)).toList(),
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Text(
|
||||
S.of(context).and_much_more_to_come,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NewPrimaryButton(
|
||||
onPressed: () {
|
||||
try {
|
||||
launchUrl(Uri.https("blog.cakewallet.com"));
|
||||
} catch (_) {}
|
||||
},
|
||||
text: S.of(context).view_more_info,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.primary),
|
||||
NewPrimaryButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VersionNumberHeader extends StatelessWidget {
|
||||
VersionNumberHeader({super.key, required this.version});
|
||||
|
||||
final String version;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Theme.of(context).colorScheme.primary, width: 2),
|
||||
borderRadius: BorderRadius.circular(99999999)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/images/cake_logo_dark.svg",
|
||||
height: 32,
|
||||
width: 32,
|
||||
colorFilter:
|
||||
ColorFilter.mode(Theme.of(context).colorScheme.onSurface, BlendMode.srcIn),
|
||||
),
|
||||
Text(
|
||||
version,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface, fontSize: 36),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChangelogItemWidget extends StatelessWidget {
|
||||
const ChangelogItemWidget({super.key, required this.item});
|
||||
|
||||
final ChangelogItem item;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
if (item.iconFilename.isNotEmpty)
|
||||
SvgPicture.asset(
|
||||
"$changelogIconLocation/${item.iconFilename}.svg",
|
||||
height: 36,
|
||||
width: 36,
|
||||
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
if (item.title.isNotEmpty)
|
||||
Text(
|
||||
item.title,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
if (item.description.isNotEmpty)
|
||||
Text(item.description,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
))
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class CoinControlListItem extends StatelessWidget {
|
||||
CoinControlListItem({
|
||||
required this.note,
|
||||
required this.amount,
|
||||
required this.fiatAmount,
|
||||
required this.address,
|
||||
required this.isSending,
|
||||
required this.isFrozen,
|
||||
required this.isChange,
|
||||
required this.isSilentPayment,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
required this.isLoading,
|
||||
this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
final String note;
|
||||
final String amount;
|
||||
final String fiatAmount;
|
||||
final String address;
|
||||
final bool isSending;
|
||||
final bool isFrozen;
|
||||
final bool isChange;
|
||||
final bool isSilentPayment;
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final bool isLoading;
|
||||
final Function()? onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height:70,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst ? Radius.circular(12) : Radius.circular(0),
|
||||
bottom: isLast ? Radius.circular(12) : Radius.circular(0),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
_getLeading(context),
|
||||
// NewSimpleCheckbox(value: isSending, onChanged: (value){onCheckBoxTap?.call();}),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Row(
|
||||
spacing:4,
|
||||
children: [
|
||||
Text(amount, style: TextStyle(fontSize:14,fontWeight: FontWeight.w400,color: Theme.of(context).colorScheme.onSurface),),
|
||||
if(address.toLowerCase().contains("mweb")) // hack carried over from old ui, we really should just have a boolean in the object
|
||||
SvgPicture.asset(
|
||||
"assets/new-ui/address-type-picker-icons/mweb.svg",
|
||||
width: 18,
|
||||
height: 18,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
if(isSilentPayment)
|
||||
SvgPicture.asset(
|
||||
"assets/new-ui/address-type-picker-icons/silent.svg",
|
||||
width: 18,
|
||||
height: 18,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
),
|
||||
AutoSizeText(
|
||||
'${address.substring(0, 5)}...${address.substring(address.length - 5)}',
|
||||
style: TextStyle(fontSize:12,fontWeight: FontWeight.w400,color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
fiatAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
"assets/new-ui/arrow_right.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getLeading(BuildContext context) {
|
||||
if(isLoading) {
|
||||
return CircularProgressIndicator(color: Theme.of(context).colorScheme.primary,);
|
||||
}
|
||||
|
||||
if(isFrozen) {
|
||||
return SvgPicture.asset("assets/new-ui/frozen.svg");
|
||||
}
|
||||
|
||||
return NewSimpleCheckbox(value: isSending, onChanged: (value){onCheckBoxTap?.call();});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/themes/core/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class CoinActionButton extends StatelessWidget {
|
||||
const CoinActionButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.action,
|
||||
});
|
||||
|
||||
final SvgPicture icon;
|
||||
final String label;
|
||||
final VoidCallback action;
|
||||
|
||||
static const sizeFactor = 0.16;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size.width * sizeFactor;
|
||||
final double effectiveSize = min(size, 80);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: effectiveSize,
|
||||
height: effectiveSize,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.customColors.cardGradientColorPrimary,
|
||||
context.customColors.cardGradientColorSecondary
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
action();
|
||||
},
|
||||
icon: icon,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/swap_page.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/lnurl.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
import '../../../pages/receive_page.dart';
|
||||
import '../../../pages/scan_page.dart';
|
||||
import 'coin_action_button.dart';
|
||||
|
||||
class CoinActionRow extends StatelessWidget {
|
||||
const CoinActionRow({super.key, this.lightningMode = false, this.showSwap = true});
|
||||
|
||||
final bool lightningMode;
|
||||
final bool showSwap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: MediaQuery.of(context).size.width * 0.05,
|
||||
children: [
|
||||
CoinActionButton(
|
||||
icon: SvgPicture.asset(
|
||||
"assets/new-ui/send.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
label: S.of(context).send,
|
||||
action: () {
|
||||
if (FeatureFlag.hasNewUiExtraPages) {
|
||||
final sendPage = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
unspentCoinType:
|
||||
lightningMode ? UnspentCoinType.lightning : UnspentCoinType.nonMweb,
|
||||
),
|
||||
);
|
||||
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
builder: (context) {
|
||||
return Material(
|
||||
child: ModalNavigator(
|
||||
rootPage: sendPage,
|
||||
parentContext: context,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
Map<String, dynamic>? args;
|
||||
if (lightningMode) args = {'coinTypeToSpendFrom' : UnspentCoinType.lightning};
|
||||
Navigator.of(context).pushNamed(Routes.send, arguments: args);
|
||||
}
|
||||
},
|
||||
),
|
||||
CoinActionButton(
|
||||
icon: SvgPicture.asset(
|
||||
"assets/new-ui/receive.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
label: S.of(context).receive,
|
||||
action: () async {
|
||||
if (FeatureFlag.hasNewUiExtraPages) {
|
||||
final page = getIt.get<NewReceivePage>(param1: lightningMode);
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
builder: (context) {
|
||||
return Material(child: ModalNavigator(parentContext:context,rootPage: page));
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// ToDo: (Konsti) refactor as part of the derivation PR (I hate myself for it)
|
||||
if (lightningMode) {
|
||||
await getIt<WalletAddressListViewModel>().setAddressType(
|
||||
bitcoin!.getOptionToType(bitcoin!.getBitcoinLightningReceivePageOption()));
|
||||
} else {
|
||||
await getIt<WalletAddressListViewModel>().setAddressType(
|
||||
bitcoin!.getOptionToType(bitcoin!.getBitcoinSegwitPageOption()));
|
||||
}
|
||||
Navigator.of(context).pushNamed(Routes.addressPage);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (showSwap)
|
||||
CoinActionButton(
|
||||
icon: SvgPicture.asset(
|
||||
"assets/new-ui/exchange.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
label: S.of(context).swap,
|
||||
action: () {
|
||||
final page = getIt.get<NewSwapPage>(param2: lightningMode ? CryptoCurrency.btcln : null);
|
||||
if (FeatureFlag.hasNewUiExtraPages) {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => Material(
|
||||
child: ModalNavigator(
|
||||
rootPage: page,
|
||||
parentContext: context,
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(Routes.exchange);
|
||||
}
|
||||
},
|
||||
),
|
||||
CoinActionButton(
|
||||
icon: SvgPicture.asset(
|
||||
"assets/new-ui/scan.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
label: S.of(context).scan,
|
||||
action: () => _onPressedScan(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPressedScan(BuildContext context) async {
|
||||
if (false && FeatureFlag.hasNewUiExtraPages) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => FractionallySizedBox(
|
||||
heightFactor: 0.9,
|
||||
child: ScanPage(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final code = await presentQRScanner(context);
|
||||
|
||||
if (code == null || code.isEmpty) return;
|
||||
|
||||
late final PaymentRequest req;
|
||||
var unspentCoinType = UnspentCoinType.any;
|
||||
if (SendViewModelBase.isNonZeroAmountLightningInvoice(code)) {
|
||||
unspentCoinType = UnspentCoinType.lightning;
|
||||
final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(getBolt11Amount(code) ?? 0));
|
||||
req = PaymentRequest(code, amount, "", "", "");
|
||||
} else if (SendViewModelBase.isLnurlInvoice(code)) {
|
||||
unspentCoinType = UnspentCoinType.lightning;
|
||||
final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(await LNURL.getPayRequestAmount(code) ?? 0));
|
||||
req = PaymentRequest(code, amount, "", "", "");
|
||||
} else if (OpenCryptoPayService.isOpenCryptoPayQR(code)) {
|
||||
req = PaymentRequest(code, "", "", "", "");
|
||||
} else {
|
||||
final uri = Uri.tryParse(code);
|
||||
if (uri == null) return;
|
||||
req = PaymentRequest.fromUri(uri);
|
||||
}
|
||||
|
||||
final sendPage = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: req,
|
||||
unspentCoinType: unspentCoinType,
|
||||
),
|
||||
);
|
||||
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
builder: (context) => Material(
|
||||
child: ModalNavigator(
|
||||
rootPage: sendPage,
|
||||
parentContext: context,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnonpayHistoryTile extends StatelessWidget {
|
||||
const AnonpayHistoryTile(
|
||||
{super.key,
|
||||
required this.provider,
|
||||
required this.createdAt,
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
required this.roundedTop,
|
||||
required this.roundedBottom,
|
||||
required this.bottomSeparator});
|
||||
|
||||
final String provider;
|
||||
final String createdAt;
|
||||
final String amount;
|
||||
final String currency;
|
||||
final bool roundedTop;
|
||||
final bool roundedBottom;
|
||||
final bool bottomSeparator;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HistoryTileBase(
|
||||
title: provider,
|
||||
date: createdAt,
|
||||
amount: amount + " " + currency,
|
||||
leadingIcon: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset('assets/images/trocador.png', width: 36, height: 36)),
|
||||
amountFiat: "",
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: bottomSeparator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/receive_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/swap_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
enum AssetDetailsModalModes { normal, ltcTransparent, ltcPrivate }
|
||||
|
||||
class AssetDetailsModal extends StatelessWidget {
|
||||
const AssetDetailsModal(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.chainTitle,
|
||||
required this.subtitle,
|
||||
required this.amount,
|
||||
required this.currencyTitle,
|
||||
required this.fiatAmount,
|
||||
required this.iconPath,
|
||||
required this.chainIconPath,
|
||||
required this.mode,
|
||||
required this.wallet,
|
||||
required this.showSwap,
|
||||
this.asset});
|
||||
|
||||
final String title;
|
||||
final CryptoCurrency? asset;
|
||||
final String chainTitle;
|
||||
final String subtitle;
|
||||
final String amount;
|
||||
final String currencyTitle;
|
||||
final String fiatAmount;
|
||||
final String iconPath;
|
||||
final String chainIconPath;
|
||||
final WalletBase wallet;
|
||||
final bool showSwap;
|
||||
final AssetDetailsModalModes mode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: "",
|
||||
trailingIcon: Icon(Icons.close),
|
||||
onTrailingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
Column(
|
||||
spacing: 15,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 75,
|
||||
height: 75,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (iconPath.isNotEmpty)
|
||||
Image.asset(iconPath, width: 75, height: 75)
|
||||
else
|
||||
Container(
|
||||
width: 75,
|
||||
height: 75,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(99999)),
|
||||
child: Center(
|
||||
child: Text(
|
||||
title.substring(0, 2),
|
||||
style: TextStyle(
|
||||
fontSize: 28, color: Theme.of(context).colorScheme.onPrimary),
|
||||
)),
|
||||
),
|
||||
if (chainIconPath.isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: Colors.black)),
|
||||
color: Colors.white),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: SvgPicture.asset(
|
||||
chainIconPath,
|
||||
width: 18,
|
||||
height: 18,
|
||||
colorFilter:
|
||||
ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
||||
),
|
||||
)))
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(999999999)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
|
||||
child: Text(
|
||||
chainTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (subtitle.isNotEmpty)
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
Column(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
amount,
|
||||
style: TextStyle(fontSize: 28),
|
||||
),
|
||||
Text(currencyTitle,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant))
|
||||
],
|
||||
),
|
||||
Text(
|
||||
fiatAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 20, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 20,
|
||||
children: [
|
||||
if (mode == AssetDetailsModalModes.ltcTransparent)
|
||||
AssetDetailsModalBottomButton(
|
||||
iconPath: "assets/new-ui/mask.svg",
|
||||
title: S.of(context).mask,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
depositToL2(context);
|
||||
},
|
||||
),
|
||||
if (mode == AssetDetailsModalModes.ltcPrivate)
|
||||
AssetDetailsModalBottomButton(
|
||||
iconPath: "assets/new-ui/unmask.svg",
|
||||
title: S.of(context).unmask,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
withdrawFromL2(context);
|
||||
},
|
||||
),
|
||||
AssetDetailsModalBottomButton(
|
||||
iconPath: "assets/new-ui/send.svg",
|
||||
title: S.of(context).send,
|
||||
onPressed: () => openPage<NewSendPage>(
|
||||
context,
|
||||
param1: SendPageParams(
|
||||
initialCurrency: asset,
|
||||
unspentCoinType: unspentCoinType,
|
||||
),
|
||||
),
|
||||
),
|
||||
AssetDetailsModalBottomButton(
|
||||
iconPath: "assets/new-ui/receive.svg",
|
||||
title: S.of(context).receive,
|
||||
onPressed: () async {
|
||||
if (mode == AssetDetailsModalModes.ltcPrivate) {
|
||||
await bitcoin!.setAddressType(
|
||||
wallet,
|
||||
bitcoin!
|
||||
.getOptionToType(bitcoin!.getLitecoinMwebReceivePageOption()));
|
||||
}
|
||||
openPage<NewReceivePage>(context, param2: asset);
|
||||
},
|
||||
),
|
||||
if (showSwap && mode != AssetDetailsModalModes.ltcPrivate)
|
||||
AssetDetailsModalBottomButton(
|
||||
iconPath: "assets/new-ui/exchange.svg",
|
||||
title: S.of(context).swap,
|
||||
onPressed: () => openPage<NewSwapPage>(context, param2: asset),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox()
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
UnspentCoinType get unspentCoinType {
|
||||
switch (mode) {
|
||||
case AssetDetailsModalModes.ltcTransparent:
|
||||
return UnspentCoinType.nonMweb;
|
||||
case AssetDetailsModalModes.ltcPrivate:
|
||||
return UnspentCoinType.mweb;
|
||||
case AssetDetailsModalModes.normal:
|
||||
return UnspentCoinType.any;
|
||||
}
|
||||
}
|
||||
|
||||
void openPage<T extends Object>(BuildContext context, {dynamic param1, dynamic param2}) {
|
||||
final page = getIt.get<T>(param1: param1, param2: param2);
|
||||
showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
builder: (context) {
|
||||
return Material(child: ModalNavigator(parentContext: context, rootPage: page as Widget));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> depositToL2(BuildContext context) async {
|
||||
PaymentRequest? paymentRequest = null;
|
||||
|
||||
final depositAddress = bitcoin!.getUnusedMwebAddress(wallet);
|
||||
if ((depositAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$depositAddress"));
|
||||
}
|
||||
|
||||
final page = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: UnspentCoinType.nonMweb,
|
||||
mode: SendPageModes.mwebDeposit,
|
||||
));
|
||||
showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(128),
|
||||
builder: (context) {
|
||||
return FractionallySizedBox(
|
||||
heightFactor: 0.65,
|
||||
child: ModalNavigator(parentContext: context, rootPage: Material(child: page)));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> withdrawFromL2(BuildContext context) async {
|
||||
PaymentRequest? paymentRequest = null;
|
||||
UnspentCoinType unspentCoinType = UnspentCoinType.any;
|
||||
final withdrawAddress = bitcoin!.getUnusedSegwitAddress(wallet);
|
||||
|
||||
if ((withdrawAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$withdrawAddress"));
|
||||
}
|
||||
unspentCoinType = UnspentCoinType.mweb;
|
||||
|
||||
final page = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: unspentCoinType,
|
||||
mode: SendPageModes.mwebWithdrawal,
|
||||
));
|
||||
showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(128),
|
||||
builder: (context) {
|
||||
return FractionallySizedBox(
|
||||
heightFactor: 0.65,
|
||||
child: ModalNavigator(parentContext: context, rootPage: Material(child: page)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class AssetDetailsModalBottomButton extends StatelessWidget {
|
||||
const AssetDetailsModalBottomButton(
|
||||
{super.key, required this.iconPath, required this.title, required this.onPressed});
|
||||
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
ModernButton.svg(
|
||||
onPressed: onPressed,
|
||||
svgPath: iconPath,
|
||||
size: 60,
|
||||
iconSize: 32,
|
||||
),
|
||||
Text(title)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/asset_details_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class AssetTile extends StatelessWidget {
|
||||
const AssetTile(
|
||||
{super.key,
|
||||
required this.balance,
|
||||
required this.chainIconPath,
|
||||
this.showSecondary = false,
|
||||
this.title,
|
||||
this.trailingText,
|
||||
this.modalMode = AssetDetailsModalModes.normal,
|
||||
required this.wallet, required this.showSwap});
|
||||
|
||||
final BalanceRecord balance;
|
||||
final bool showSecondary;
|
||||
final bool showSwap;
|
||||
final String chainIconPath;
|
||||
final String? title;
|
||||
final String? trailingText;
|
||||
final AssetDetailsModalModes modalMode;
|
||||
final WalletBase wallet;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconPath = _getIconPath();
|
||||
|
||||
return GestureDetector(
|
||||
onTap: (){
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return AssetDetailsModal(
|
||||
showSwap: showSwap,
|
||||
asset: balance.asset,
|
||||
title: title ?? balance.asset.fullName ?? balance.asset.name,
|
||||
chainTitle: _getChainTitle(),
|
||||
subtitle: trailingText ?? "",
|
||||
amount: showSecondary ? balance.secondAvailableBalance : balance.availableBalance,
|
||||
currencyTitle: balance.asset.title,
|
||||
fiatAmount: showSecondary
|
||||
? balance.fiatSecondAvailableBalance
|
||||
: balance.fiatAvailableBalance,
|
||||
iconPath: balance.asset.iconPath ?? "",
|
||||
chainIconPath: chainIconPath,
|
||||
mode: modalMode,
|
||||
wallet: wallet,
|
||||
);
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 6.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
Theme.of(context).colorScheme.surfaceContainer,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
child: Stack(
|
||||
children: [
|
||||
if((iconPath).isNotEmpty)
|
||||
Image.asset(iconPath)
|
||||
else
|
||||
Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(99999)),
|
||||
child: Center(
|
||||
child: Text(
|
||||
balance.asset.name.substring(0, 2),
|
||||
style: TextStyle(
|
||||
fontSize: 20, color: Theme.of(context).colorScheme.onPrimary),
|
||||
)),
|
||||
),
|
||||
if (chainIconPath.isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(5),side: BorderSide(color: Colors.black)),
|
||||
color: Colors.white),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: SvgPicture.asset(
|
||||
chainIconPath,
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter:
|
||||
ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
||||
),
|
||||
)))
|
||||
],
|
||||
)),
|
||||
SizedBox(width: 8.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
title ?? balance.asset.fullName ?? balance.asset.name,
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
if(trailingText != null)
|
||||
Text(
|
||||
trailingText!,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
"${showSecondary ?balance.secondAvailableBalance: balance.availableBalance} ${balance.formattedAssetTitle.safeSubString(0, 6)}",
|
||||
maxLines:1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
showSecondary ? balance.fiatSecondAvailableBalance : balance.fiatAvailableBalance,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getChainTitle() {
|
||||
try {
|
||||
return CryptoCurrency.fromString(wallet.currency.tag ??wallet.currency.title).title;
|
||||
} catch(e) {
|
||||
return wallet.currency.title;
|
||||
}
|
||||
}
|
||||
|
||||
String _getIconPath() {
|
||||
if(balance.asset == CryptoCurrency.baseEth)
|
||||
return "assets/images/crypto/ethereum.webp";
|
||||
|
||||
return balance.asset.iconPath ?? "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'assets_section.dart';
|
||||
import 'history_section.dart';
|
||||
|
||||
class AssetsHistorySectionTab {
|
||||
final String title;
|
||||
final Widget content;
|
||||
|
||||
AssetsHistorySectionTab(this.title, this.content);
|
||||
}
|
||||
|
||||
class AssetsHistorySection extends StatefulWidget {
|
||||
AssetsHistorySection({super.key, required this.dashboardViewModel, required this.nftViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final NFTViewModel nftViewModel;
|
||||
|
||||
@override
|
||||
State<AssetsHistorySection> createState() => _AssetsHistorySectionState();
|
||||
}
|
||||
|
||||
class _AssetsHistorySectionState extends State<AssetsHistorySection> {
|
||||
List<AssetsHistorySectionTab> tabs = [];
|
||||
int _selectedTab = 0;
|
||||
|
||||
void reloadTabs() {
|
||||
final oldTabLength = tabs.length;
|
||||
tabs = [
|
||||
if (widget.dashboardViewModel.balanceViewModel.isHomeScreenSettingsEnabled || (widget.dashboardViewModel.hasMweb && widget.dashboardViewModel.mwebEnabled))
|
||||
AssetsHistorySectionTab(
|
||||
S.current.assets,
|
||||
AssetsSection(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
)),
|
||||
AssetsHistorySectionTab(
|
||||
S.current.history,
|
||||
HistorySection(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
)),
|
||||
if (isNFTACtivatedChain(widget.dashboardViewModel.wallet.type,
|
||||
chainId: widget.dashboardViewModel.wallet.chainId))
|
||||
AssetsHistorySectionTab(S.current.nfts, NFTListingPage(nftViewModel: widget.nftViewModel))
|
||||
];
|
||||
if (oldTabLength != tabs.length) {
|
||||
setState(() {
|
||||
_selectedTab = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
reloadTabs();
|
||||
|
||||
reaction((_)=>widget.dashboardViewModel.balanceViewModel.formattedBalances, (value) {
|
||||
reloadTabs();
|
||||
});
|
||||
reaction((_)=>widget.dashboardViewModel.mwebEnabled, (_)=>reloadTabs());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
if(tabs.length>1)
|
||||
AssetsTopBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
tabs: tabs.map((item) => item.title).toList(),
|
||||
onTabChange: (index) {
|
||||
setState(() {
|
||||
_selectedTab = index;
|
||||
});
|
||||
},
|
||||
selectedTab: _selectedTab,
|
||||
),
|
||||
tabs[_selectedTab].content
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/asset_details_modal.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
||||
import 'asset_tile.dart';
|
||||
|
||||
class AssetsSection extends StatelessWidget {
|
||||
const AssetsSection({super.key, required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 64.0),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
final hasMweb = dashboardViewModel.hasMweb && dashboardViewModel.mwebEnabled;
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length + (hasMweb ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
return Observer(builder: (context) {
|
||||
if (hasMweb) {
|
||||
return AssetTile(
|
||||
showSwap: dashboardViewModel.isEnabledSwapAction,
|
||||
balance: dashboardViewModel.balanceViewModel.formattedBalances.first,
|
||||
showSecondary: index > 0 ? true : false,
|
||||
wallet: dashboardViewModel.wallet,
|
||||
chainIconPath: index > 0 ? dashboardViewModel.wallet.currency.chainIconPath! : "",
|
||||
trailingText: index > 0 ? "MWEB" : null,
|
||||
modalMode: index > 0
|
||||
? AssetDetailsModalModes.ltcPrivate
|
||||
: AssetDetailsModalModes.ltcTransparent,
|
||||
title: index > 0 ? "Litecoin Private" : null,
|
||||
);
|
||||
}
|
||||
|
||||
final balance = dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return AssetTile(
|
||||
showSwap: dashboardViewModel.isEnabledSwapAction,
|
||||
balance: balance,
|
||||
wallet: dashboardViewModel.wallet,
|
||||
chainIconPath: _getChainIconPath(),
|
||||
);
|
||||
});
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO refactor chainIconPath to get rid of this ugly thing. it's needed because arbitrum's wallet currency isn't arb
|
||||
String _getChainIconPath() {
|
||||
try {
|
||||
return CryptoCurrency.fromString(dashboardViewModel.wallet.currency.tag ??dashboardViewModel.wallet.currency.title).chainIconPath!;
|
||||
} catch(e) {
|
||||
return dashboardViewModel.wallet.currency.chainIconPath ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/line_tab_switcher.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class AssetsTopBar extends StatelessWidget {
|
||||
const AssetsTopBar({
|
||||
super.key,
|
||||
required this.onTabChange,
|
||||
required this.selectedTab,
|
||||
required this.tabs, required this.dashboardViewModel,
|
||||
});
|
||||
|
||||
final void Function(int) onTabChange;
|
||||
final int selectedTab;
|
||||
final List<String> tabs;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasTokenSettingsButton = tabs[selectedTab] == S.of(context).assets && dashboardViewModel.balanceViewModel.isHomeScreenSettingsEnabled;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0, bottom: 12.0, left: 12.0, right: 18.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
LineTabSwitcher(
|
||||
tabs: tabs,
|
||||
onTabChange: onTabChange,
|
||||
selectedTab: selectedTab,
|
||||
),
|
||||
Opacity(
|
||||
opacity: hasTokenSettingsButton ? 1 : 0,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (hasTokenSettingsButton) {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.homeSettings,
|
||||
arguments: dashboardViewModel.balanceViewModel,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
SvgPicture.asset("assets/new-ui/options_slider.svg", colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary,BlendMode.srcIn)),
|
||||
Text(
|
||||
S.of(context).tokens,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile_base.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HistoryOrderTile extends StatelessWidget {
|
||||
const HistoryOrderTile(
|
||||
{super.key,
|
||||
required this.date,
|
||||
required this.amount,
|
||||
required this.amountFiat,
|
||||
required this.roundedTop,
|
||||
required this.roundedBottom,
|
||||
required this.bottomSeparator,
|
||||
});
|
||||
|
||||
final String date;
|
||||
final String amount;
|
||||
final String amountFiat;
|
||||
final bool roundedTop;
|
||||
final bool roundedBottom;
|
||||
final bool bottomSeparator;
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return HistoryTileBase(
|
||||
title: "Order",
|
||||
date: date,
|
||||
amount: amount,
|
||||
amountFiat: amountFiat,
|
||||
leadingIcon:Image.asset("assets/images/cakepay.png"),
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: bottomSeparator,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/anonpay_history_tile.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_order_tile.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_trade_tile.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/payjoin_history_tile.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class HistorySection extends StatelessWidget {
|
||||
const HistorySection({super.key, required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Observer(
|
||||
builder: (_) => (dashboardViewModel.items.isEmpty &&
|
||||
dashboardViewModel.status is! SyncingSyncStatus)
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Text(S.of(context).transactions_will_appear_here,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)))
|
||||
: ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: dashboardViewModel.items.length,
|
||||
itemBuilder: (context, index) => Observer(builder: (_) {
|
||||
final prevItem = index == 0 ? null : dashboardViewModel.items[index - 1];
|
||||
final item = dashboardViewModel.items[index];
|
||||
final nextItem = index == dashboardViewModel.items.length - 1
|
||||
? null
|
||||
: dashboardViewModel.items[index + 1];
|
||||
|
||||
final roundedBottom = (nextItem == null || nextItem is DateSectionItem);
|
||||
final roundedTop = (prevItem == null || prevItem is DateSectionItem);
|
||||
|
||||
if (item is TransactionListItem) {
|
||||
final transaction = item.transaction;
|
||||
final transactionType = dashboardViewModel.getTransactionType(transaction);
|
||||
|
||||
if (item.hasTokens && item.assetOfTransaction == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
CryptoCurrency? asset;
|
||||
if (transaction.additionalInfo["isLightning"] == true)
|
||||
asset = CryptoCurrency.btcln;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.transactionDetails, arguments: transaction),
|
||||
child: HistoryTile(
|
||||
title: item.formattedTitle + transactionType,
|
||||
date: DateFormat('HH:mm').format(transaction.date),
|
||||
amount: item.formattedCryptoAmount,
|
||||
amountFiat: item.formattedFiatAmount,
|
||||
roundedBottom: roundedBottom,
|
||||
roundedTop: roundedTop,
|
||||
bottomSeparator: !roundedBottom,
|
||||
direction: item.transaction.direction,
|
||||
pending: item.transaction.isPending,
|
||||
asset: asset,
|
||||
),
|
||||
);
|
||||
} else if (item is TradeListItem) {
|
||||
final trade = item.trade;
|
||||
|
||||
final tradeFrom = trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
|
||||
|
||||
final tradeTo = trade.toRaw >= 0 ? trade.to : trade.userCurrencyTo;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.tradeDetails, arguments: trade),
|
||||
child: HistoryTradeTile(
|
||||
from: tradeFrom!,
|
||||
to: tradeTo!,
|
||||
date: DateFormat('HH:mm').format(item.trade.createdAt!),
|
||||
amount: trade.amountFormatted(),
|
||||
receiveAmount: trade.receiveAmountFormatted(),
|
||||
roundedBottom: roundedBottom,
|
||||
roundedTop: roundedTop,
|
||||
bottomSeparator: !roundedBottom,
|
||||
swapState: trade.state,
|
||||
),
|
||||
);
|
||||
} else if (item is DateSectionItem) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 8.0, bottom: 8.0, top: 18.0),
|
||||
child: Text(DateFormatter.convertDateTimeToReadableString(item.date),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)));
|
||||
}else if(item is OrderListItem){
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.orderDetails, arguments: item.order),
|
||||
child: HistoryOrderTile(
|
||||
date: DateFormat('HH:mm').format(item.order.createdAt),
|
||||
amount: item.orderFormattedAmount,
|
||||
amountFiat: "USD 0.00",
|
||||
roundedBottom: roundedBottom,
|
||||
roundedTop: roundedTop,
|
||||
bottomSeparator: !roundedBottom,
|
||||
),
|
||||
);
|
||||
} else if (item is PayjoinTransactionListItem) {
|
||||
final session = item.session;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.payjoinDetails,
|
||||
arguments: [item.sessionId, item.transaction],
|
||||
),
|
||||
child: PayjoinHistoryTile(
|
||||
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
|
||||
amount: dashboardViewModel.appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(session.amount.toInt(), CryptoCurrency.btc),
|
||||
currency: item.transaction?.from ?? "BTC",
|
||||
state: item.status,
|
||||
isSending: session.isSenderSession,
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: !roundedBottom),
|
||||
);
|
||||
} else if (item is AnonpayTransactionListItem) {
|
||||
final transactionInfo = item.transaction;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
|
||||
child: AnonpayHistoryTile(
|
||||
provider: transactionInfo.provider,
|
||||
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
|
||||
amount: transactionInfo.fiatAmount?.toString() ??
|
||||
(transactionInfo.amountTo?.toString() ?? ''),
|
||||
currency: transactionInfo.fiatAmount != null
|
||||
? transactionInfo.fiatEquiv ?? ''
|
||||
: CryptoCurrency.fromFullName(transactionInfo.coinTo).name.toUpperCase(),
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: !roundedBottom));
|
||||
} else
|
||||
return Text(item.runtimeType.toString());
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile_base.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class HistoryTile extends StatelessWidget {
|
||||
const HistoryTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.date,
|
||||
required this.amount,
|
||||
required this.amountFiat,
|
||||
required this.roundedTop,
|
||||
required this.roundedBottom,
|
||||
required this.direction,
|
||||
required this.pending,
|
||||
required this.bottomSeparator,
|
||||
this.asset,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String date;
|
||||
final String amount;
|
||||
final String amountFiat;
|
||||
final bool roundedTop;
|
||||
final bool roundedBottom;
|
||||
final bool bottomSeparator;
|
||||
final TransactionDirection direction;
|
||||
final bool pending;
|
||||
final CryptoCurrency? asset;
|
||||
|
||||
String _getDirectionIcon() {
|
||||
if (pending) {
|
||||
return direction == TransactionDirection.incoming
|
||||
? 'assets/new-ui/history-receiving.svg'
|
||||
: 'assets/new-ui/history-sending.svg';
|
||||
} else {
|
||||
return direction == TransactionDirection.incoming
|
||||
? 'assets/new-ui/history-received.svg'
|
||||
: 'assets/new-ui/history-sent.svg';
|
||||
}
|
||||
}
|
||||
|
||||
String _getDirectionIconToken() {
|
||||
if (pending) {
|
||||
return direction == TransactionDirection.incoming
|
||||
? 'assets/new-ui/history-receiving.svg'
|
||||
: 'assets/new-ui/history-sending.svg';
|
||||
} else {
|
||||
return direction == TransactionDirection.incoming
|
||||
? 'assets/new-ui/token-received.svg'
|
||||
: 'assets/new-ui/token-sent.svg';
|
||||
}
|
||||
}
|
||||
|
||||
Color? _getPrimaryTextColor() {
|
||||
}
|
||||
|
||||
Widget _getLeadingIcon(BuildContext context) {
|
||||
if (asset == CryptoCurrency.btcln) {
|
||||
return Stack(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/images/lightning-icon.svg",
|
||||
width: 34,
|
||||
height: 34,
|
||||
),
|
||||
Positioned(
|
||||
top: 20,
|
||||
left: 20,
|
||||
child: Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(color: Theme.of(context).colorScheme.onInverseSurface, shape: BoxShape.circle),
|
||||
child: SvgPicture.asset(
|
||||
_getDirectionIconToken(),
|
||||
colorFilter: ColorFilter.mode(
|
||||
direction == TransactionDirection.outgoing
|
||||
? Theme
|
||||
.of(context)
|
||||
.colorScheme
|
||||
.inverseSurface.withAlpha(175)
|
||||
: Colors.green,
|
||||
BlendMode.srcIn),
|
||||
width: 14,
|
||||
height: 14,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return SvgPicture.asset(_getDirectionIcon(),
|
||||
colorFilter: ColorFilter.mode(
|
||||
direction == TransactionDirection.outgoing
|
||||
? Theme.of(context).colorScheme.inverseSurface.withAlpha(175)
|
||||
: Colors.green,
|
||||
BlendMode.srcIn));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HistoryTileBase(
|
||||
title: title,
|
||||
date: date,
|
||||
amount: amount,
|
||||
amountFiat: amountFiat,
|
||||
leadingIcon: _getLeadingIcon(context),
|
||||
primaryTextColor: _getPrimaryTextColor(),
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: bottomSeparator,
|
||||
asset: asset,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HistoryTileBase extends StatelessWidget {
|
||||
const HistoryTileBase({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.date,
|
||||
required this.amount,
|
||||
required this.leadingIcon,
|
||||
required this.amountFiat,
|
||||
required this.roundedTop,
|
||||
required this.roundedBottom,
|
||||
required this.bottomSeparator,
|
||||
this.primaryTextColor,
|
||||
this.asset,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String date;
|
||||
final String amount;
|
||||
final String amountFiat;
|
||||
final Widget leadingIcon;
|
||||
final bool roundedTop;
|
||||
final bool roundedBottom;
|
||||
final bool bottomSeparator;
|
||||
final Color? primaryTextColor;
|
||||
final CryptoCurrency? asset;
|
||||
|
||||
// String _getDirectionIcon() {
|
||||
// if (pending) {
|
||||
// return direction == TransactionDirection.incoming
|
||||
// ? 'assets/new-ui/history-receiving.svg'
|
||||
// : 'assets/new-ui/history-sending.svg';
|
||||
// } else {
|
||||
// return direction == TransactionDirection.incoming
|
||||
// ? 'assets/new-ui/history-received.svg'
|
||||
// : 'assets/new-ui/history-sent.svg';
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Widget _getLeadingIcon(BuildContext context) {
|
||||
// if (asset == CryptoCurrency.btcln) {
|
||||
// return Stack(
|
||||
// children: [
|
||||
// Image.asset(
|
||||
// asset!.iconPath!,
|
||||
// width: 34,
|
||||
// height: 34,
|
||||
// ),
|
||||
// Positioned(
|
||||
// top: 20,
|
||||
// left: 20,
|
||||
// child: SvgPicture.asset(
|
||||
// 'assets/new-ui/chain_badges/lightning.svg',
|
||||
// width: 16,
|
||||
// height: 16,
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// return SvgPicture.asset(_getDirectionIcon());
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(roundedTop ? 22 : 0),
|
||||
topRight: Radius.circular(roundedTop ? 22 : 0),
|
||||
bottomLeft: Radius.circular(roundedBottom ? 22 : 0),
|
||||
bottomRight: Radius.circular(roundedBottom ? 22 : 0),
|
||||
)),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
width: 36,
|
||||
child: leadingIcon,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
|
||||
Text(date,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(amount,
|
||||
style: TextStyle(
|
||||
color: primaryTextColor ??
|
||||
Theme.of(context).colorScheme.onSurface)),
|
||||
Text(amountFiat, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if(bottomSeparator) Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 56, right: 16),
|
||||
child: Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant.withAlpha(175)),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile_base.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HistoryTradeTile extends StatelessWidget {
|
||||
const HistoryTradeTile(
|
||||
{super.key,
|
||||
required this.date,
|
||||
required this.amount,
|
||||
required this.receiveAmount,
|
||||
required this.roundedTop,
|
||||
required this.roundedBottom,
|
||||
required this.bottomSeparator,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.swapState});
|
||||
|
||||
final CryptoCurrency from;
|
||||
final CryptoCurrency to;
|
||||
final String date;
|
||||
final String amount;
|
||||
final String receiveAmount;
|
||||
final bool roundedTop;
|
||||
final bool roundedBottom;
|
||||
final bool bottomSeparator;
|
||||
final TradeState swapState;
|
||||
|
||||
Widget _getLeadingStack(BuildContext context) {
|
||||
double currencyIconSize = 22.0;
|
||||
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
width: 50,
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.asset(_getIconPath(from),
|
||||
width: currencyIconSize, height: currencyIconSize),
|
||||
Positioned(
|
||||
top: currencyIconSize / 2,
|
||||
left: currencyIconSize / 2,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
shape: BoxShape.circle),
|
||||
child: Image.asset(_getIconPath(to),
|
||||
width: currencyIconSize, height: currencyIconSize))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return HistoryTileBase(
|
||||
title:
|
||||
"${from.toString()}${from == CryptoCurrency.btcln ? "-LN" : ""} → ${to.toString()}${to == CryptoCurrency.btcln ? "-LN" : ""}",
|
||||
date: date,
|
||||
amount: amount,
|
||||
amountFiat: receiveAmount,
|
||||
leadingIcon: _getLeadingStack(context),
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: bottomSeparator,
|
||||
);
|
||||
}
|
||||
|
||||
String _getIconPath(CryptoCurrency currency) {
|
||||
if (currency.iconPath != null) {
|
||||
return currency.iconPath!;
|
||||
}
|
||||
|
||||
if (currency.name.isNotEmpty) {
|
||||
final currencyFromName = CryptoCurrency.fromString(currency.name);
|
||||
if (currencyFromName.iconPath != null) {
|
||||
return currencyFromName.iconPath!;
|
||||
}
|
||||
}
|
||||
|
||||
if (currency.title.isNotEmpty) {
|
||||
final currencyFromTitle = CryptoCurrency.fromString(currency.title);
|
||||
if (currencyFromTitle.iconPath != null) {
|
||||
return currencyFromTitle.iconPath!;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO approporiate fallback
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PayjoinHistoryTile extends StatelessWidget {
|
||||
const PayjoinHistoryTile(
|
||||
{super.key,
|
||||
required this.createdAt,
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
required this.state,
|
||||
required this.isSending,
|
||||
required this.roundedTop,
|
||||
required this.roundedBottom,
|
||||
required this.bottomSeparator});
|
||||
|
||||
final String createdAt;
|
||||
final String amount;
|
||||
final String currency;
|
||||
final String state;
|
||||
final bool isSending;
|
||||
final bool roundedTop;
|
||||
final bool roundedBottom;
|
||||
final bool bottomSeparator;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HistoryTileBase(
|
||||
title: "${isSending ? S.of(context).outgoing : S.of(context).incoming} Payjoin",
|
||||
date: createdAt,
|
||||
amount: amount + " " + currency,
|
||||
leadingIcon: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset(
|
||||
'assets/images/payjoin.png',
|
||||
width: 36,
|
||||
height: 36,
|
||||
),
|
||||
),
|
||||
amountFiat: state,
|
||||
roundedTop: roundedTop,
|
||||
roundedBottom: roundedBottom,
|
||||
bottomSeparator: bottomSeparator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class BalanceCardAction {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const BalanceCardAction({required this.label, required this.icon, required this.onTap});
|
||||
}
|
||||
|
||||
class BalanceCard extends StatelessWidget {
|
||||
const BalanceCard({
|
||||
super.key,
|
||||
required this.width,
|
||||
required this.design,
|
||||
this.gradient,
|
||||
this.borderRadius = 20,
|
||||
this.selected = false,
|
||||
this.accountName = "",
|
||||
this.accountBalance = "",
|
||||
this.balance = "",
|
||||
this.fiatBalance = "",
|
||||
this.assetName = "",
|
||||
this.designSwitchDuration = const Duration(),
|
||||
this.actions = const [],
|
||||
this.capitalizeAssetName = true,
|
||||
this.onCustomizeTapped,
|
||||
this.accountIndex
|
||||
});
|
||||
|
||||
final double width;
|
||||
final double borderRadius;
|
||||
final Gradient? gradient;
|
||||
final String accountBalance;
|
||||
final String accountName;
|
||||
final String balance;
|
||||
final String fiatBalance;
|
||||
final int? accountIndex;
|
||||
final bool capitalizeAssetName;
|
||||
final String assetName;
|
||||
final bool selected;
|
||||
final CardDesign design;
|
||||
final List<BalanceCardAction> actions;
|
||||
final Duration designSwitchDuration;
|
||||
final VoidCallback? onCustomizeTapped;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Duration textFadeDuration = Duration(milliseconds: 80);
|
||||
final double iconWidth = width * 0.15;
|
||||
|
||||
final bool showText = accountBalance.isNotEmpty ||
|
||||
accountName.isNotEmpty ||
|
||||
balance.isNotEmpty ||
|
||||
fiatBalance.isNotEmpty ||
|
||||
assetName.isNotEmpty;
|
||||
|
||||
final height = width * 0.62;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: designSwitchDuration,
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: ShapeDecoration(
|
||||
gradient: gradient ?? design.gradient,
|
||||
shape: RoundedSuperellipseBorder(
|
||||
side: const BorderSide(color: Color(0x44FFFFFF), width: 1),
|
||||
borderRadius: BorderRadiusGeometry.circular(borderRadius),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
child: design.backgroundType == CardDesignBackgroundTypes.svgFull
|
||||
? ClipRSuperellipse(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
key: ValueKey(design.imagePath),
|
||||
child: SvgPicture.asset(
|
||||
design.imagePath,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('svgFullOff'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(width * 0.05),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (showText)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if(accountName.isNotEmpty || accountBalance.isNotEmpty)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
if (accountIndex != null)
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: design.colors.textColor),
|
||||
child: Text("$accountIndex."),
|
||||
),
|
||||
),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w500, color: design.colors.textColor),
|
||||
child: Text(accountName),
|
||||
),
|
||||
],
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: selected ? 0 : 1,
|
||||
duration: textFadeDuration,
|
||||
child: Text(
|
||||
accountBalance,
|
||||
style: TextStyle(color: design.colors.textColor, fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: selected ? 1 : 0,
|
||||
duration: textFadeDuration,
|
||||
child: AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
key: ValueKey("$balance ${assetName.toUpperCase()}"),
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context)
|
||||
.style
|
||||
.copyWith(color: design.colors.textColor, fontSize: 28, fontWeight: FontWeight.w500, letterSpacing: -0.4),
|
||||
child: Text(balance),
|
||||
),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context)
|
||||
.style
|
||||
.copyWith(color: design.colors.textColorSecondary, fontSize: 28, fontWeight: FontWeight.w400, letterSpacing: -0.4),
|
||||
child: Text(capitalizeAssetName ? assetName.toUpperCase() : assetName.toLowerCase()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: designSwitchDuration,
|
||||
style: DefaultTextStyle.of(context).style.copyWith(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: design.colors.textColorSecondary),
|
||||
child: AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
key: ValueKey(fiatBalance),
|
||||
fiatBalance,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Container(),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
key: ValueKey(actions.toString()),
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: actions.map(getBalanceCardActionButton).toList(),
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: designSwitchDuration,
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
child: design.backgroundType == CardDesignBackgroundTypes.svgIcon
|
||||
? SvgPicture.asset(
|
||||
design.imagePath,
|
||||
key: const ValueKey('svgIcon'),
|
||||
height: iconWidth,
|
||||
width: iconWidth,
|
||||
colorFilter: ColorFilter.mode(
|
||||
design.colors.backgroundImageColor.withAlpha(80),
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('svgIconOff'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 8,
|
||||
top: 8,
|
||||
child: AnimatedOpacity(
|
||||
duration: designSwitchDuration,
|
||||
opacity: onCustomizeTapped == null ? 0 : 1,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onCustomizeTapped,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
"assets/new-ui/3dots_vertical.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(design.colors.textColorSecondary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getBalanceCardActionButton(BalanceCardAction action) => GestureDetector(
|
||||
onTap: action.onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: design.colors.backgroundImageColor.withAlpha(75),
|
||||
borderRadius: BorderRadius.circular(10000000),
|
||||
),
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.only(left: 10, right: 5, top: 5, bottom: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Text(
|
||||
action.label,
|
||||
style: TextStyle(color: design.colors.textColor, fontSize: 16),
|
||||
),
|
||||
),
|
||||
Icon(action.icon, color: design.colors.textColorSecondary),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
import 'balance_card.dart';
|
||||
|
||||
class CardsView extends StatefulWidget {
|
||||
const CardsView(
|
||||
{super.key,
|
||||
required this.dashboardViewModel,
|
||||
required this.accountListViewModel,
|
||||
required this.lightningMode,
|
||||
required this.onCompactModeBackgroundCardsTapped, required this.onCustomizeTapped});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final MoneroAccountListViewModel? accountListViewModel;
|
||||
final VoidCallback onCompactModeBackgroundCardsTapped;
|
||||
final VoidCallback onCustomizeTapped;
|
||||
final bool lightningMode;
|
||||
|
||||
@override
|
||||
_CardsViewState createState() => _CardsViewState();
|
||||
}
|
||||
|
||||
class _CardsViewState extends State<CardsView> {
|
||||
late int _selectedIndex;
|
||||
bool isFirstBuild = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedIndex = widget.dashboardViewModel.cardOrder.length - 1;
|
||||
reaction(
|
||||
(_) => widget.dashboardViewModel.cardOrder.values.toList(),
|
||||
(_) => setState(() {
|
||||
_selectedIndex = widget.dashboardViewModel.cardOrder.length - 1;
|
||||
}));
|
||||
}
|
||||
|
||||
static const Duration animDuration = Duration(milliseconds: 200);
|
||||
static const int compactModeTreshold = 4;
|
||||
static const int maxCards = 5;
|
||||
late final double cardWidth = MediaQuery.of(context).size.width * 0.878;
|
||||
late final double effectiveCardWidth = min(cardWidth, 768);
|
||||
|
||||
Widget _buildCard(int visualIndex, int realIndex, int numCards, double parentWidth,
|
||||
Map<int, int> order, bool compactMode, double overlapAmount) {
|
||||
final baseTop = overlapAmount * (numCards - 1);
|
||||
final scaleFactor = compactMode ? 1 : 0.96;
|
||||
|
||||
final howFarBehind = (_selectedIndex - visualIndex + numCards) % numCards;
|
||||
final scale = pow(scaleFactor, howFarBehind).toDouble();
|
||||
|
||||
final top = baseTop - (howFarBehind * overlapAmount);
|
||||
|
||||
final left = (parentWidth - effectiveCardWidth) / 2.0;
|
||||
|
||||
return AnimatedPositioned(
|
||||
key: ValueKey("$visualIndex $realIndex"),
|
||||
duration: animDuration,
|
||||
curve: Curves.easeOut,
|
||||
top: top,
|
||||
left: left,
|
||||
child: AnimatedScale(
|
||||
duration: animDuration,
|
||||
curve: Curves.easeOut,
|
||||
scale: scale,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// printV(visualIndex);
|
||||
if (compactMode && visualIndex != 0) {
|
||||
widget.onCompactModeBackgroundCardsTapped();
|
||||
} else if(!compactMode) {
|
||||
setState(() {
|
||||
if (widget.accountListViewModel != null)
|
||||
widget.accountListViewModel!
|
||||
.select(widget.accountListViewModel!.accounts[realIndex]);
|
||||
_selectedIndex = visualIndex;
|
||||
});
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (_selectedIndex == visualIndex) {
|
||||
widget.dashboardViewModel.balanceViewModel.switchBalanceValue();
|
||||
};
|
||||
HapticFeedback.heavyImpact();
|
||||
},
|
||||
child: Observer(builder: (_) {
|
||||
if(realIndex >= (widget.accountListViewModel?.accounts.length ?? 1)) {
|
||||
return Container();
|
||||
}
|
||||
final account = widget.accountListViewModel?.accounts[realIndex];
|
||||
|
||||
// The second balance should always be the lightning balance
|
||||
// printV(widget.dashboardViewModel.balanceViewModel.formattedBalances.first.availableBalance);
|
||||
final walletBalanceRecord = widget.dashboardViewModel.balanceViewModel.formattedBalances
|
||||
.elementAtOrNull(widget.lightningMode ? 1 : 0);
|
||||
|
||||
late final String walletBalance;
|
||||
late final String walletFiatBalance;
|
||||
if (widget.dashboardViewModel.mwebEnabled && widget.dashboardViewModel.hasMweb) {
|
||||
if(widget.dashboardViewModel.balanceViewModel.displayMode == BalanceDisplayMode.hiddenBalance) {
|
||||
walletBalance = '●●●●●●';
|
||||
walletFiatBalance = '●●●●●●';
|
||||
} else {
|
||||
walletBalance = walletBalanceRecord?.combinedAvailableBalance ?? "0";
|
||||
walletFiatBalance = walletBalanceRecord?.combinedFiatAvailableBalance ?? "0.00";
|
||||
}
|
||||
} else {
|
||||
walletBalance = walletBalanceRecord?.availableBalance ?? "0";
|
||||
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalance ?? "${widget.dashboardViewModel.appStore.settingsStore.fiatCurrency.title} 0.00";
|
||||
}
|
||||
|
||||
// the card designs is empty if widget gets built before it loads.
|
||||
// should get populated before user sees anything
|
||||
final CardDesign cardDesign;
|
||||
if (widget.dashboardViewModel.cardDesigns.isEmpty ||
|
||||
realIndex >= widget.dashboardViewModel.cardDesigns.length)
|
||||
cardDesign = CardDesign.genericDefault;
|
||||
else if(widget.lightningMode)
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex + 1];
|
||||
else
|
||||
cardDesign = widget.dashboardViewModel.cardDesigns[realIndex];
|
||||
|
||||
final String accountName;
|
||||
final String accountBalance;
|
||||
if (account == null) {
|
||||
accountName = "";
|
||||
accountBalance = "";
|
||||
} else {
|
||||
accountName = account.label;
|
||||
accountBalance = account.balance ?? "0.00";
|
||||
}
|
||||
|
||||
final List<BalanceCardAction> actions = widget.lightningMode
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_deposit,
|
||||
icon: Icons.arrow_downward,
|
||||
onTap: depositToL2,
|
||||
),
|
||||
BalanceCardAction(
|
||||
label: S.current.bitcoin_lightning_withdraw,
|
||||
icon: Icons.arrow_upward,
|
||||
onTap: withdrawFromL2,
|
||||
)
|
||||
]
|
||||
: widget.dashboardViewModel.isEnabledTradeAction
|
||||
? [
|
||||
BalanceCardAction(
|
||||
label: S.current.buy,
|
||||
icon: Icons.arrow_forward,
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.buySellPage),
|
||||
)
|
||||
]
|
||||
: [];
|
||||
|
||||
return BalanceCard(
|
||||
width: effectiveCardWidth,
|
||||
accountName: accountName,
|
||||
accountBalance: accountBalance,
|
||||
designSwitchDuration: Duration(milliseconds: 150),
|
||||
assetName: walletBalanceRecord?.formattedAssetTitle ?? assetTitleFallback,
|
||||
capitalizeAssetName: _shouldCapitalizeAssetName(),
|
||||
balance: walletBalance,
|
||||
fiatBalance: walletFiatBalance,
|
||||
selected: _selectedIndex == visualIndex,
|
||||
onCustomizeTapped: _selectedIndex == visualIndex ? widget.onCustomizeTapped : null,
|
||||
design: cardDesign,
|
||||
actions: actions,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String get assetTitleFallback => widget.dashboardViewModel.appStore.amountParsingProxy.getCryptoSymbol(
|
||||
widget.lightningMode
|
||||
? CryptoCurrency.btcln
|
||||
: widget.dashboardViewModel.wallet.currency);
|
||||
|
||||
bool _shouldCapitalizeAssetName() {
|
||||
if(widget.dashboardViewModel.wallet.type != WalletType.bitcoin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (widget.dashboardViewModel.settingsStore.displayAmountsInSatoshi) {
|
||||
case BitcoinAmountDisplayMode.satoshi:
|
||||
return false;
|
||||
case BitcoinAmountDisplayMode.satoshiForLightning:
|
||||
return !widget.lightningMode;
|
||||
case BitcoinAmountDisplayMode.bitcoin:
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
double _getBoxHeight(int numCards, double overlapAmount) {
|
||||
return
|
||||
/* height of initial card */
|
||||
(2 / 3.2) * (effectiveCardWidth) +
|
||||
/* height of bg card * amount of bg cards */
|
||||
overlapAmount * ((numCards) - 1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (context) {
|
||||
final parentWidth = MediaQuery.of(context).size.width;
|
||||
final children = <Widget>[];
|
||||
|
||||
int numCards = widget.dashboardViewModel.wallet.type == WalletType.bitcoin
|
||||
? 1
|
||||
: widget.dashboardViewModel.cardDesigns.length;
|
||||
if(numCards == 0) numCards = 1;
|
||||
|
||||
if (_selectedIndex >= (numCards)) {
|
||||
_selectedIndex = 0;
|
||||
}
|
||||
|
||||
Map<int, int> order = widget.dashboardViewModel.cardOrder.length != numCards
|
||||
? Map<int, int>.fromEntries(
|
||||
List.generate(numCards, (i) => MapEntry(i, i)),
|
||||
)
|
||||
: widget.dashboardViewModel.cardOrder;
|
||||
|
||||
for (int i = min(numCards - 1, maxCards); i >= 0; i--) {
|
||||
int visualIndex = (_selectedIndex - i + numCards) % numCards;
|
||||
if (order[visualIndex] == null) {
|
||||
order = Map<int, int>.fromEntries(
|
||||
List.generate(numCards, (i) => MapEntry(i, i)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final bool compactMode = numCards >= compactModeTreshold;
|
||||
final double overlapAmount = compactMode ? 5.0 : 46.0;
|
||||
for (int i = min(numCards - 1, maxCards); i >= 0; i--) {
|
||||
int visualIndex = (_selectedIndex - i + numCards) % numCards;
|
||||
|
||||
int realIndex = order[visualIndex]!;
|
||||
|
||||
if (visualIndex == _selectedIndex &&
|
||||
widget.accountListViewModel != null &&
|
||||
widget.accountListViewModel?.selected.label !=
|
||||
widget.accountListViewModel?.accounts[realIndex].label) {
|
||||
widget.accountListViewModel!
|
||||
.select(widget.accountListViewModel!.accounts[realIndex]);
|
||||
}
|
||||
|
||||
children.add(_buildCard(
|
||||
visualIndex, realIndex, numCards, parentWidth, order, compactMode, overlapAmount));
|
||||
}
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
width: double.infinity,
|
||||
height: _getBoxHeight(numCards, overlapAmount),
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 200),
|
||||
transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child),
|
||||
child: SizedBox(
|
||||
key: ValueKey(_getBoxHeight(numCards, overlapAmount)),
|
||||
width: double.infinity,
|
||||
height: _getBoxHeight(numCards, overlapAmount),
|
||||
child: Stack(alignment: Alignment.center, children: children),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> depositToL2() async {
|
||||
PaymentRequest? paymentRequest = null;
|
||||
|
||||
if (widget.dashboardViewModel.type == WalletType.litecoin) {
|
||||
final depositAddress = bitcoin!.getUnusedMwebAddress(widget.dashboardViewModel.wallet);
|
||||
if ((depositAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$depositAddress"));
|
||||
}
|
||||
} else if (widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
final depositAddress =
|
||||
await bitcoin!.getUnusedSpakDepositAddress(widget.dashboardViewModel.wallet);
|
||||
if ((depositAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$depositAddress"));
|
||||
}
|
||||
}
|
||||
|
||||
if (FeatureFlag.hasNewUiExtraPages && widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
final page = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: UnspentCoinType.nonMweb,
|
||||
mode: SendPageModes.lightningDeposit,
|
||||
));
|
||||
showCupertinoModalBottomSheet(context: context, barrierColor: Colors.black.withAlpha(128), builder: (context){
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: SizedBox(
|
||||
height:MediaQuery.of(context).size.height*0.6,
|
||||
child:ModalNavigator(parentContext:context,rootPage: Material(child: page))
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.send,
|
||||
arguments: {
|
||||
'paymentRequest': paymentRequest,
|
||||
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> withdrawFromL2() async {
|
||||
PaymentRequest? paymentRequest = null;
|
||||
UnspentCoinType unspentCoinType = UnspentCoinType.any;
|
||||
final withdrawAddress = bitcoin!.getUnusedSegwitAddress(widget.dashboardViewModel.wallet);
|
||||
|
||||
if (widget.dashboardViewModel.type == WalletType.litecoin) {
|
||||
if ((withdrawAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$withdrawAddress"));
|
||||
}
|
||||
unspentCoinType = UnspentCoinType.mweb;
|
||||
} else if (widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
if ((withdrawAddress?.isNotEmpty ?? false)) {
|
||||
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$withdrawAddress"));
|
||||
}
|
||||
unspentCoinType = UnspentCoinType.lightning;
|
||||
}
|
||||
|
||||
if (FeatureFlag.hasNewUiExtraPages && widget.dashboardViewModel.type == WalletType.bitcoin) {
|
||||
final page = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: paymentRequest,
|
||||
unspentCoinType: unspentCoinType,
|
||||
mode: SendPageModes.lightningWithdrawal,
|
||||
));
|
||||
showCupertinoModalBottomSheet(context: context, barrierColor: Colors.black.withAlpha(128), builder: (context){
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: SizedBox(
|
||||
height:MediaQuery.of(context).size.height*0.6,
|
||||
child:ModalNavigator(parentContext:context,rootPage: Material(child: page))
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.send,
|
||||
arguments: {
|
||||
'paymentRequest': paymentRequest,
|
||||
'coinTypeToSpendFrom': unspentCoinType,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class MwebAd extends StatelessWidget {
|
||||
const MwebAd({super.key, required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (_) {
|
||||
if (!dashboardViewModel.shouldShowMwebAd) return SizedBox.shrink();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12, top: 24),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.mwebSettings),
|
||||
child: Container(
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/new-ui/settings_row_icons/mweb.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(
|
||||
S.of(context).mweb_ad,
|
||||
softWrap: true,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.arrow_forward_ios,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => dashboardViewModel.dismissMwebAd(false),
|
||||
child: Text(
|
||||
S.of(context).do_not_show_anymore,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class ChainIcon extends StatelessWidget {
|
||||
const ChainIcon(
|
||||
{super.key,
|
||||
required this.iconPath,
|
||||
required this.dashboardViewModel,
|
||||
required this.isSyncHeavy});
|
||||
|
||||
final String iconPath;
|
||||
final bool isSyncHeavy;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final progress = dashboardViewModel.status.progress();
|
||||
final done = !isSyncHeavy || progress >= 1;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
AnimatedOpacity(
|
||||
duration: Duration(milliseconds: 100),
|
||||
opacity: done ? 0 : 1,
|
||||
child: CircularProgressIndicator(
|
||||
value: progress,
|
||||
color: Color(0xFFFFB84E),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
AnimatedScale(
|
||||
duration: Duration(milliseconds: 150),
|
||||
scale: done ? 1 : 0.8,
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
child: SvgPicture.asset(
|
||||
key: ValueKey(progress >= 1),
|
||||
iconPath,
|
||||
width: 36,
|
||||
height: 36,
|
||||
colorFilter: ColorFilter.mode(
|
||||
done
|
||||
? Theme.of(context).colorScheme.primary.withOpacity(0.2)
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class LightningSwitcher extends StatelessWidget {
|
||||
const LightningSwitcher(
|
||||
{super.key, required this.lightningMode, required this.onLightningSwitchPress});
|
||||
|
||||
final bool lightningMode;
|
||||
final VoidCallback onLightningSwitchPress;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
onLightningSwitchPress();
|
||||
},
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(borderRadius: BorderRadiusGeometry.circular(900.0)),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
width: 70,
|
||||
height: 36,
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
alignment: Alignment.centerRight,
|
||||
margin: EdgeInsets.only(left: lightningMode ? 36 : 2),
|
||||
duration: Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 32,
|
||||
height: 32,
|
||||
// height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9999990.0)),
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
spacing: 2.0,
|
||||
children: [
|
||||
SizedBox(),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: SvgPicture.asset(
|
||||
key: ValueKey(lightningMode),
|
||||
'assets/new-ui/switcher-bitcoin.svg',
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: SvgPicture.asset(
|
||||
key: ValueKey(lightningMode),
|
||||
'assets/new-ui/switcher-lightning.svg',
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PulsingDot extends StatefulWidget {
|
||||
const PulsingDot({super.key,});
|
||||
|
||||
|
||||
final double size = 5;
|
||||
final Color color = const Color(0xFFFFC414);
|
||||
final Duration fadeOutDuration = const Duration(milliseconds: 2000);
|
||||
final Duration restDuration = const Duration(milliseconds: 2000);
|
||||
final double restOpacity = 0.3;
|
||||
|
||||
@override
|
||||
State<PulsingDot> createState() => _PulsingDotState();
|
||||
}
|
||||
|
||||
class _PulsingDotState extends State<PulsingDot>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.fadeOutDuration,
|
||||
reverseDuration: Duration.zero,
|
||||
value: 1.0,
|
||||
);
|
||||
_loop();
|
||||
}
|
||||
|
||||
Future<void> _loop() async {
|
||||
while (mounted) {
|
||||
await controller.animateTo(
|
||||
widget.restOpacity,
|
||||
curve: Curves.easeOutQuad,
|
||||
duration: widget.fadeOutDuration,
|
||||
);
|
||||
await Future.delayed(widget.restDuration);
|
||||
if(!mounted) return;
|
||||
controller.value = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: controller,
|
||||
child: Container(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import 'package:cake_wallet/core/sync_status_title.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/pulsing_dot.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class SyncBar extends StatelessWidget {
|
||||
SyncBar({super.key, required this.dashboardViewModel, required this.isSyncHeavy});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final bool isSyncHeavy;
|
||||
|
||||
static const failStatuses = [
|
||||
FailedSyncStatus,
|
||||
LostConnectionSyncStatus,
|
||||
TimedOutSyncStatus,
|
||||
UnsupportedSyncStatus,
|
||||
];
|
||||
|
||||
static const progressStatuses = [
|
||||
SyncingSyncStatus,
|
||||
NotConnectedSyncStatus,
|
||||
SyncronizingSyncStatus,
|
||||
AttemptingSyncStatus,
|
||||
StartingScanSyncStatus,
|
||||
AttemptingScanSyncStatus,
|
||||
SyncedTipSyncStatus,
|
||||
ProcessingSyncStatus,
|
||||
ConnectingSyncStatus,
|
||||
ConnectedSyncStatus,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final status = dashboardViewModel.status;
|
||||
final Widget? icon = _getIcon(context, status.runtimeType);
|
||||
|
||||
return Expanded(
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
if (!_showFullBar())
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 6,
|
||||
children: [
|
||||
if (dashboardViewModel.isTorEnabled)
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tor.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
if (_showDot()) PulsingDot(),
|
||||
],
|
||||
),
|
||||
if (_showFullBar())
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync);
|
||||
},
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
key: ValueKey(status.runtimeType),
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999),
|
||||
border: _getBorder(context, status.runtimeType),
|
||||
color: _getBackgroundColor(context, status.runtimeType),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (icon != null) icon,
|
||||
// if (dashboardViewModel.silentPaymentsScanningActive &&
|
||||
// progressStatuses.contains(status.runtimeType)) ...[
|
||||
// Text(
|
||||
// "${(status.progress() * 100).toInt()}%",
|
||||
// style: TextStyle(fontSize: 12, color: Color(0xFFEFBA5E)),
|
||||
// ),
|
||||
// Text(
|
||||
// "·",
|
||||
// style: TextStyle(fontSize: 12),
|
||||
// )
|
||||
// ],
|
||||
Text(
|
||||
syncStatusTitle(
|
||||
status, dashboardViewModel.settingsStore.syncStatusDisplayMode),
|
||||
style: _getTextStyle(context, status.runtimeType),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Color? _getBackgroundColor(BuildContext context, Type status) {
|
||||
if (failStatuses.contains(status)) {
|
||||
return Theme.of(context).colorScheme.errorContainer.withAlpha(64);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Border? _getBorder(BuildContext context, Type status) {
|
||||
if (progressStatuses.contains(status)) {
|
||||
return Border.all(color: Theme.of(context).colorScheme.surfaceContainerHigh, width: 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
TextStyle? _getTextStyle(BuildContext context, Type status) {
|
||||
if (failStatuses.contains(status)) {
|
||||
return TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.w400, color: Theme.of(context).colorScheme.error);
|
||||
} else {
|
||||
return TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant);
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _getIcon(BuildContext context, Type status) {
|
||||
if (status == LostConnectionSyncStatus || status == FailedSyncStatus) {
|
||||
return SvgPicture.asset(
|
||||
"assets/new-ui/offline.svg",
|
||||
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.error, BlendMode.srcIn),
|
||||
);
|
||||
}
|
||||
|
||||
if (failStatuses.contains(status)) {
|
||||
return SvgPicture.asset(
|
||||
"assets/new-ui/warning.svg",
|
||||
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.error, BlendMode.srcIn),
|
||||
);
|
||||
}
|
||||
|
||||
final List<Widget> children = [];
|
||||
|
||||
if (dashboardViewModel.isTorEnabled) {
|
||||
children.add(SvgPicture.asset("assets/new-ui/tor_sync.svg",
|
||||
colorFilter: ColorFilter.mode(Color(0xFF8A38F5), BlendMode.srcIn)));
|
||||
}
|
||||
if (dashboardViewModel.hasMweb) {
|
||||
children.add(SvgPicture.asset(
|
||||
"assets/new-ui/mweb_sync.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
));
|
||||
}
|
||||
if (dashboardViewModel.hasSilentPayments) {
|
||||
children.add(SvgPicture.asset(
|
||||
"assets/new-ui/silent_sync.svg",
|
||||
colorFilter: ColorFilter.mode(Color(0xFFEFBA5E), BlendMode.srcIn),
|
||||
));
|
||||
}
|
||||
|
||||
return Row(
|
||||
spacing: 8,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
bool _showFullBar() {
|
||||
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus) return false;
|
||||
return isSyncHeavy || failStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
}
|
||||
|
||||
bool _showDot() {
|
||||
return !isSyncHeavy && progressStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user