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:
Omar Hatem
2026-03-11 04:26:57 +02:00
committed by GitHub
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
775 changed files with 60901 additions and 29638 deletions
+18 -21
View File
@@ -1,4 +1,5 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/material.dart';
@@ -32,7 +33,6 @@ class AuthPage extends StatefulWidget {
class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
final _key = GlobalKey<ScaffoldState>();
final _pinCodeKey = GlobalKey<PinCodeState>();
final _backArrowImageDarkTheme = Image.asset('assets/images/close_button.png');
ReactionDisposer? _reaction;
Flushbar<void>? _authBar;
Flushbar<void>? _progressBar;
@@ -127,26 +127,23 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
appBar: CupertinoNavigationBar(
leading: widget.closable
? Container(
padding: EdgeInsets.only(top: 10),
child: SizedBox(
height: 37,
width: 37,
child: InkWell(
onTap: () => Navigator.of(context).pop(),
child: _backArrowImageDarkTheme,
),
))
: Container(),
backgroundColor: Theme.of(context).colorScheme.surface,
border: null),
resizeToAvoidBottomInset: false,
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin), (_) => null,
widget.authViewModel.pinLength, false, _pinCodeKey));
return Container(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Scaffold(
key: _key,
appBar: CupertinoNavigationBar(
leading: widget.closable
? ModernButton(size: 36, icon: Icon(Icons.close), onPressed: Navigator.of(context).pop, iconColor: Theme.of(context).colorScheme.onSurfaceVariant)
: Container(),
backgroundColor: Theme.of(context).colorScheme.surface,
border: null),
resizeToAvoidBottomInset: false,
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin), (_) => null,
widget.authViewModel.pinLength, false, _pinCodeKey)),
),
);
}
void dismissFlushBar(Flushbar<dynamic>? bar) {
+55 -1
View File
@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:ui';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
@@ -12,6 +13,7 @@ import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -116,7 +118,9 @@ class BackupPage extends BasePage {
leftButtonText: S.of(context).seed_alert_back,
actionRightButton: () async {
Navigator.of(dialogContext).pop();
final backup = await backupViewModelBase.exportBackup();
final backupFuture = backupViewModelBase.exportBackup();
showPersistentActionOverlay(context, backupFuture, text: S.of(context).creating_backup);
final backup = await backupFuture;
if (backup == null) {
return;
@@ -178,3 +182,53 @@ class BackupPage extends BasePage {
}
}
}
void showPersistentActionOverlay(BuildContext context, Future action, {String text = ""}) async {
final navigator = Navigator.of(context, rootNavigator: true);
showPopUp(
useRootNavigator: true,
context: context,
builder: (_) => PersistentActionOverlay(text: text),
barrierDismissible: false);
try {
await action;
} finally {
// this way even if action throws, it'll still pop and not softlock the app
navigator.pop();
}
}
class PersistentActionOverlay extends StatelessWidget {
const PersistentActionOverlay({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: EdgeInsets.all(20),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.surfaceContainer.withAlpha(80)),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 36, height: 36, child: CupertinoActivityIndicator()),
if (text.isNotEmpty)
Text(text, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w500))
],
),
),
))),
),
);
}
}
@@ -1,75 +1,89 @@
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
import 'package:mobx/mobx.dart';
class EditBackupPasswordPage extends BasePage {
EditBackupPasswordPage(this.editBackupPasswordViewModel)
: textEditingController = TextEditingController() {
textEditingController.text = editBackupPasswordViewModel.backupPassword;
class EditBackupPasswordPage extends StatefulWidget {
EditBackupPasswordPage(this.editBackupPasswordViewModel);
final EditBackupPasswordViewModel editBackupPasswordViewModel;
@override
State<EditBackupPasswordPage> createState() => _EditBackupPasswordPageState();
}
class _EditBackupPasswordPageState extends State<EditBackupPasswordPage> {
final textEditingController = TextEditingController();
@override
void initState() {
super.initState();
textEditingController.text = widget.editBackupPasswordViewModel.backupPassword;
textEditingController
.addListener(() => editBackupPasswordViewModel.backupPassword = textEditingController.text);
reaction((_) => editBackupPasswordViewModel.backupPassword, (_) {
if (textEditingController.text != editBackupPasswordViewModel.backupPassword) {
textEditingController.text = editBackupPasswordViewModel.backupPassword;
.addListener(() {
if(textEditingController.text != widget.editBackupPasswordViewModel.backupPassword) {
widget.editBackupPasswordViewModel.backupPassword = textEditingController.text;
}
});
}
final EditBackupPasswordViewModel editBackupPasswordViewModel;
final TextEditingController textEditingController;
@override
String get title => S.current.edit_backup_password;
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 24, right: 24),
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Observer(
builder: (_) => BaseTextFormField(
autofocus: true,
enableSuggestions: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
controller: textEditingController,
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
Widget build(BuildContext context) {
return Column(children: [
ModalTopBar(
title: S.of(context).edit_backup_password,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
left: 24, right: 24, bottom: MediaQuery.of(context).viewInsets.bottom),
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Observer(
builder: (_) => TextFormField(
enableSuggestions: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
controller: textEditingController,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
),
),
),
Positioned(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () => onSave(context),
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: !widget.editBackupPasswordViewModel.canSave,
),
),
bottom: 24,
left: 0,
right: 0,
)
],
),
),
Positioned(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () => onSave(context),
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: !editBackupPasswordViewModel.canSave,
),
),
bottom: 24,
left: 0,
right: 0,
)
],
),
),
);
]);
}
void onSave(BuildContext context) {
FocusScope.of(context).unfocus();
showPopUp<void>(
context: context,
builder: (dialogContext) {
@@ -79,7 +93,8 @@ class EditBackupPasswordPage extends BasePage {
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () async {
await editBackupPasswordViewModel.save();
widget.editBackupPasswordViewModel.backupPassword = textEditingController.text;
await widget.editBackupPasswordViewModel.save();
Navigator.of(dialogContext).pop();
if (context.mounted) {
Navigator.of(context).pop();
+25 -24
View File
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/themes/core/theme_store.dart';
@@ -82,20 +83,17 @@ abstract class BasePage extends StatelessWidget {
}
return MergeSemantics(
child: SizedBox(
height: 37,
width: 37,
child: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: S.of(context).seed_alert_back,
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
child: ModernButton(
size: 37,
icon: Icon(CupertinoIcons.back),
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
onPressed: () => onClose(context),
child: backButton(context),
),
),
),
@@ -180,11 +178,11 @@ abstract class BasePage extends StatelessWidget {
child: Observer(
builder: (context) {
final backgroundImage = getIt.get<SettingsStore>().backgroundImage;
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
image: backgroundImage.isNotEmpty
? DecorationImage(
image: FileImage(File(backgroundImage)),
@@ -193,21 +191,24 @@ abstract class BasePage extends StatelessWidget {
: null,
// color: Colors.grey[200],
),
child: Scaffold(
key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,
appBar: appBar(context),
body: SafeArea(
left: false,
right: false,
top: false,
bottom: Platform.isAndroid,
child: body(context),
child: Padding(
padding: const EdgeInsets.only(top:6.0),
child: Scaffold(
key: _scaffoldKey,
backgroundColor: pageBackgroundColor(context),
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,
appBar: appBar(context),
body: SafeArea(
left: false,
right: false,
top: false,
bottom: Platform.isAndroid,
child: body(context),
),
floatingActionButton: floatingActionButton(context),
),
floatingActionButton: floatingActionButton(context),
),
);
},
+10 -13
View File
@@ -1,7 +1,6 @@
import 'package:cake_wallet/buy/sell_buy_states.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
@@ -12,7 +11,7 @@ import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_s
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/provider_optoin_tile.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/src/widgets/trail_button.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -56,7 +55,7 @@ class BuySellPage extends BasePage {
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
AppBarStyle get appBarStyle => AppBarStyle.completelyTransparent;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
@@ -256,9 +255,7 @@ class BuySellPage extends BasePage {
}
void _setReactions(BuildContext context, BuySellViewModel buySellViewModel) {
if (_isReactionsSet) {
return;
}
if (_isReactionsSet) return;
final fiatAmountController = fiatCurrencyKey.currentState!.amountController;
final cryptoAmountController = cryptoCurrencyKey.currentState!.amountController;
@@ -269,28 +266,27 @@ class BuySellPage extends BasePage {
reaction(
(_) => buySellViewModel.wallet.name,
(String _) => _onWalletNameChange(
(_) => _onWalletNameChange(
buySellViewModel, buySellViewModel.cryptoCurrency, cryptoCurrencyKey));
reaction(
(_) => buySellViewModel.cryptoCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, buySellViewModel, cryptoCurrencyKey));
(currency) => _onCurrencyChange(currency, buySellViewModel, cryptoCurrencyKey));
reaction((_) => buySellViewModel.fiatCurrency,
(FiatCurrency currency) => _onCurrencyChange(currency, buySellViewModel, fiatCurrencyKey));
(currency) => _onCurrencyChange(currency, buySellViewModel, fiatCurrencyKey));
reaction((_) => buySellViewModel.fiatAmount, (String amount) {
reaction((_) => buySellViewModel.fiatAmount, (amount) {
if (fiatCurrencyKey.currentState!.amountController.text != amount) {
fiatCurrencyKey.currentState!.amountController.text = amount;
}
});
reaction((_) => buySellViewModel.isCryptoCurrencyAddressEnabled, (bool isEnabled) {
reaction((_) => buySellViewModel.isCryptoCurrencyAddressEnabled, (isEnabled) {
cryptoCurrencyKey.currentState!.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => buySellViewModel.cryptoAmount, (String amount) {
reaction((_) => buySellViewModel.cryptoAmount, (amount) {
if (cryptoCurrencyKey.currentState!.amountController.text != amount) {
cryptoCurrencyKey.currentState!.amountController.text = amount;
}
@@ -453,6 +449,7 @@ class BuySellPage extends BasePage {
fillColor: buySellViewModel.isBuyAction
? Theme.of(context).colorScheme.surfaceContainerLow
: Theme.of(context).colorScheme.surfaceContainer,
useSatoshis: buySellViewModel.useSatoshi,
),
);
@@ -1,12 +1,12 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cw_core/generate_name.dart';
import 'package:cake_wallet/generated/i18n.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/blockchain_height_widget.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/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
@@ -50,7 +50,7 @@ class SelectDeviceManufacturerPage extends BasePage {
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.trezor,
tag: S.current.new_tag,
// tag: S.current.new_tag,
),
if (Platform.isAndroid) ...[
_DeviceManufacturer(
@@ -60,7 +60,7 @@ class SelectDeviceManufacturerPage extends BasePage {
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.bitbox,
tag: S.current.new_tag,
// tag: S.current.new_tag,
),
],
_DeviceManufacturer(
@@ -70,7 +70,7 @@ class SelectDeviceManufacturerPage extends BasePage {
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.cupcake,
tag: S.current.new_tag,
// tag: S.current.new_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset(
@@ -79,7 +79,7 @@ class SelectDeviceManufacturerPage extends BasePage {
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.coldcard,
tag: S.current.new_tag,
// tag: S.current.new_tag,
),
// _DeviceManufacturer(
// image: SvgPicture.asset(
@@ -1,17 +1,16 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cw_core/generate_name.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.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/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
@@ -149,10 +148,9 @@ class _SelectHardwareWalletAccountFormState extends State<SelectHardwareWalletAc
padding: EdgeInsets.only(top: 10),
child: SelectButton(
image: Image.asset(
getCryptoCurrencyForWalletListItem(
getCryptoCurrencyIconForWalletListItem(
_walletHardwareRestoreVM.type,
).iconPath ??
'',
),
height: 24,
width: 24,
),
+104 -91
View File
@@ -8,8 +8,10 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
@@ -21,23 +23,28 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class ContactListPage extends BasePage {
ContactListPage(this.contactListViewModel, this.authService);
ContactListPage(this.contactListViewModel, this.authService, {this.showAddButton = false});
final ContactListViewModel contactListViewModel;
final AuthService authService;
final bool showAddButton;
@override
bool get gradientBackground => true;
@override
String get title => S.current.address_book;
@override
Widget? trailing(BuildContext context) {
if(!showAddButton) return SizedBox.shrink();
return MergeSemantics(
child: Container(
width: 32.0,
height: 32.0,
width: 36.0,
height: 36.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Semantics(
label: S.of(context).add_contact,
@@ -47,7 +54,7 @@ class ContactListPage extends BasePage {
children: <Widget>[
Icon(
Icons.add,
color: Theme.of(context).colorScheme.onSurface,
color: Theme.of(context).colorScheme.primary,
size: 22.0,
),
ButtonTheme(
@@ -111,57 +118,59 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 8),
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
return GradientBackground(
scaffold: Padding(
padding: const EdgeInsets.only(),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 8),
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
controller: _tabController,
splashFactory: NoSplash.splashFactory,
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
labelStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
unselectedLabelStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
indicatorColor: Theme.of(context).colorScheme.primary,
indicatorPadding: EdgeInsets.zero,
labelPadding: EdgeInsets.only(right: 24),
tabAlignment: TabAlignment.start,
dividerColor: Colors.transparent,
padding: EdgeInsets.zero,
tabs: [
Tab(text: S.of(context).wallets),
Tab(text: S.of(context).contact_list_contacts),
],
),
),
),
Expanded(
child: TabBarView(
controller: _tabController,
splashFactory: NoSplash.splashFactory,
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
labelStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
unselectedLabelStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
indicatorColor: Theme.of(context).colorScheme.primary,
indicatorPadding: EdgeInsets.zero,
labelPadding: EdgeInsets.only(right: 24),
tabAlignment: TabAlignment.start,
dividerColor: Colors.transparent,
padding: EdgeInsets.zero,
tabs: [
Tab(text: S.of(context).wallets),
Tab(text: S.of(context).contact_list_contacts),
children: [
Observer(
builder: (final BuildContext context) => _buildWalletContacts(context),
),
ContactListBody(
contactListViewModel: widget.contactListViewModel,
tabController: _tabController,
),
],
),
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Observer(
builder: (final BuildContext context) => _buildWalletContacts(context),
),
ContactListBody(
contactListViewModel: widget.contactListViewModel,
tabController: _tabController,
),
],
),
),
],
],
),
),
);
}
@@ -176,6 +185,7 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
}
return ListView.builder(
padding: EdgeInsets.only(bottom: 128),
itemCount: groupedContacts.length * 2,
itemBuilder: (context, index) {
if (index.isOdd) {
@@ -208,8 +218,8 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
expandedAlignment: Alignment.topLeft,
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainer,
collapsedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
collapsedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
children: groupContacts.map((contact) => generateRaw(context, contact)).toList(),
),
);
@@ -244,7 +254,7 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
behavior: HitTestBehavior.opaque,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderRadius: BorderRadius.all(Radius.circular(18)),
color: Theme.of(context).colorScheme.surfaceContainer,
),
margin: const EdgeInsets.only(top: 4, bottom: 4, left: 16, right: 16),
@@ -317,7 +327,7 @@ class _ContactListBodyState extends State<ContactListBody> {
? widget.contactListViewModel.contacts
: widget.contactListViewModel.contactsToShow;
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
backgroundColor: Colors.transparent,
body: Container(
child: FilteredList(
list: contacts,
@@ -371,7 +381,7 @@ class _ContactListBodyState extends State<ContactListBody> {
Container(
key: Key('${contact.name}'),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderRadius: BorderRadius.all(Radius.circular(18)),
color: Theme.of(context).colorScheme.surfaceContainer,
),
margin: const EdgeInsets.only(top: 4, bottom: 4, left: 16, right: 16),
@@ -431,40 +441,43 @@ class _ContactListBodyState extends State<ContactListBody> {
'assets/images/filter_icon.png',
color: Theme.of(context).colorScheme.onSurface,
);
return MergeSemantics(
child: SizedBox(
height: 58,
width: 58,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
container: true,
child: GestureDetector(
onTap: () async {
await showPopUp<void>(
context: context,
builder: (context) => FilterListWidget(
initalType: contactListViewModel.orderType,
initalAscending: contactListViewModel.ascending,
onClose: (bool ascending, FilterListOrderType type) async {
contactListViewModel.setAscending(ascending);
await contactListViewModel.setOrderType(type);
},
return Padding(
padding: const EdgeInsets.only(bottom: FeatureFlag.hasNewUi ? 48 : 0),
child: MergeSemantics(
child: SizedBox(
height: 58,
width: 58,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
container: true,
child: GestureDetector(
onTap: () async {
await showPopUp<void>(
context: context,
builder: (context) => FilterListWidget(
initalType: contactListViewModel.orderType,
initalAscending: contactListViewModel.ascending,
onClose: (bool ascending, FilterListOrderType type) async {
contactListViewModel.setAscending(ascending);
await contactListViewModel.setOrderType(type);
},
),
);
},
child: Semantics(
label: 'Transaction Filter',
button: true,
enabled: true,
child: Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: filterIcon,
),
);
},
child: Semantics(
label: 'Transaction Filter',
button: true,
enabled: true,
child: Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: filterIcon,
),
),
),
@@ -501,7 +514,7 @@ class DialogService {
alertContentTextWidget: AddressFormatter.buildSegmentedAddress(
address: contact.address,
textAlign: TextAlign.center,
walletType: cryptoCurrencyToWalletType(contact.type),
walletType: cryptoCurrencyOrTokenToWalletType(contact.type),
evenTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
decoration: TextDecoration.none,
+28 -13
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
@@ -12,8 +13,7 @@ import 'package:cw_core/crypto_currency.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/primary_button.dart';
import 'package:cake_wallet/src/widgets/address_text_field.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/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
@@ -22,18 +22,21 @@ class ContactPage extends BasePage {
: _formKey = GlobalKey<FormState>(),
_nameController = TextEditingController(),
_addressController = TextEditingController(),
_currencyTypeController = TextEditingController() {
_currencyTypeController = TextEditingController(),
addressFocusNode = FocusNode(){
_nameController.text = contactViewModel.name;
_addressController.text = contactViewModel.address;
_nameController.addListener(() => contactViewModel.name = _nameController.text);
_addressController.addListener(() => contactViewModel.address = _addressController.text);
autorun((_) => _currencyTypeController.text = contactViewModel.currency?.toString() ?? '');
autorun((_) => _currencyTypeController.text = "${contactViewModel.currency?.toString() ?? ""} ${contactViewModel.currency?.tag != null ? "(${contactViewModel.currency?.tag})" : ""}");
}
@override
String get title => S.current.contact;
FocusNode addressFocusNode;
final ContactViewModel contactViewModel;
final GlobalKey<FormState> _formKey;
final TextEditingController _nameController;
@@ -89,15 +92,17 @@ class ContactPage extends BasePage {
if (contactViewModel.currency != null)
Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
controller: _addressController,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
validator: AddressValidator(type: contactViewModel.currency!),
child: Observer(
builder: (_) {
return NewSendAddressInput(
focusNode: addressFocusNode,
displayName: contactViewModel.displayName,
addressController: _addressController,
onEditingComplete: ()=> contactViewModel.extractParsedAddress(context),
validator: AddressValidator(type: contactViewModel.currency!),
selectedCurrency: contactViewModel.currency!,
);
},
),
)
],
@@ -123,6 +128,10 @@ class ContactPage extends BasePage {
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () async {
FocusScope.of(context).unfocus();
await contactViewModel.extractParsedAddress(context);
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
@@ -187,5 +196,11 @@ class ContactPage extends BasePage {
_onContactSavedSuccessfully(context);
}
});
reaction((_)=>contactViewModel.address, (val){
if(val != _addressController.text) {
_addressController.text = val;
}
});
}
}
@@ -4,6 +4,7 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/page_indicator.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/new_main_navbar_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/widgets/evm_switcher.dart';
@@ -25,7 +26,6 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/navigation_dock.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
@@ -304,9 +304,11 @@ class _DashboardPageView extends BasePage {
),
),
),
NavigationDock(
NewMainNavBar(
dashboardViewModel: dashboardViewModel,
)
selectedIndex: 0,
onItemTap: (index) {}
),
],
),
],
@@ -8,7 +8,7 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/checkbox_widget.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/src/widgets/warning_box_widget.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -36,16 +36,16 @@ class HomeSettingsPage extends BasePage {
onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
),
),
Divider(color: Theme.of(context).colorScheme.outlineVariant),
Observer(
builder: (_) => SettingsSwitcherCell(
title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
value: _homeSettingsViewModel.pinNativeToken,
onValueChange: (_, bool value) {
_homeSettingsViewModel.setPinNativeToken(value);
},
),
),
// Divider(color: Theme.of(context).colorScheme.outlineVariant),
// Observer(
// builder: (_) => SettingsSwitcherCell(
// title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
// value: _homeSettingsViewModel.pinNativeToken,
// onValueChange: (_, bool value) {
// _homeSettingsViewModel.setPinNativeToken(value);
// },
// ),
// ),
Divider(color: Theme.of(context).colorScheme.outlineVariant),
const SizedBox(height: 20),
Row(
@@ -177,7 +177,7 @@ class AddressPage extends BasePage {
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).colorScheme.surfaceContainer),
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(18),
),
child: Column(
children: [
@@ -315,8 +315,7 @@ class AddressPage extends BasePage {
}
break;
default:
if (addressListViewModel.type == WalletType.bitcoin ||
addressListViewModel.type == WalletType.litecoin) {
if ([WalletType.bitcoin, WalletType.litecoin].contains(addressListViewModel.type)) {
addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
}
if (addressListViewModel.type == WalletType.zcash) {
@@ -22,7 +22,7 @@ class BalancePage extends StatelessWidget {
Widget build(BuildContext context) {
return Observer(
builder: (context) {
final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type, dashboardViewModel.wallet.chainId);
final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type, chainId: dashboardViewModel.wallet.chainId);
return DefaultTabController(
key: ValueKey<bool>(isNFTActivated),
length: isNFTActivated ? 2 : 1,
@@ -12,7 +12,9 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.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/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -218,7 +220,7 @@ class BalanceRowWidget extends StatelessWidget {
if (currency.isPotentialScam)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
margin: EdgeInsets.only(top: 4),
margin: const EdgeInsets.only(top: 4),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer,
borderRadius: BorderRadius.circular(8),
@@ -246,7 +248,7 @@ class BalanceRowWidget extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 26),
const SizedBox(height: 26),
Row(
children: [
Text(
@@ -259,7 +261,7 @@ class BalanceRowWidget extends StatelessWidget {
),
],
),
SizedBox(height: 8),
const SizedBox(height: 8),
AutoSizeText(
frozenBalance,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
@@ -270,14 +272,12 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
const SizedBox(height: 4),
if (!isTestnet)
Text(
frozenFiatBalance,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
height: 1,
),
style: Theme.of(context).textTheme.bodySmall!.copyWith(height: 1),
),
],
),
@@ -285,7 +285,7 @@ class BalanceRowWidget extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
const SizedBox(height: 24),
Text(
'${additionalBalanceLabel}',
textAlign: TextAlign.center,
@@ -294,7 +294,7 @@ class BalanceRowWidget extends StatelessWidget {
height: 1,
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
AutoSizeText(
additionalBalance,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
@@ -305,14 +305,12 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
const SizedBox(height: 4),
if (!isTestnet)
Text(
'${additionalFiatBalance}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
height: 1,
),
style: Theme.of(context).textTheme.bodySmall!.copyWith(height: 1),
),
],
),
@@ -335,15 +333,6 @@ class BalanceRowWidget extends StatelessWidget {
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
// boxShadow: [
// BoxShadow(
// color: Theme.of(context)
// .extension<BalancePageTheme>()!
// .cardBorderColor
// .withAlpha(50),
// spreadRadius: dashboardViewModel.getShadowSpread(),
// blurRadius: dashboardViewModel.getShadowBlur())
// ],
),
child: TextButton(
onPressed: _showToast,
@@ -362,27 +351,48 @@ class BalanceRowWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
child: Column(
children: [
Container(
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
size: 40,
),
),
const SizedBox(height: 10),
Text(
'MWEB',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
],
),
Column(
children: [
ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
size: 40,
),
const SizedBox(height: 10),
Text(
'MWEB',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
],
),
],
),
if (currency == CryptoCurrency.btc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
children: [
SvgPicture.asset(
'assets/images/lightning-icon.svg',
width: 40,
height: 40,
),
const SizedBox(height: 10),
Text(
'Lightning',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
],
),
],
),
@@ -394,15 +404,11 @@ class BalanceRowWidget extends StatelessWidget {
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/cryptos/litecoin#mweb"),
mode: LaunchMode.externalApplication,
),
onTap: onPressedHelp,
child: Row(
children: [
Text(
'${secondAvailableBalanceLabel}',
secondAvailableBalanceLabel,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color:
@@ -436,7 +442,7 @@ class BalanceRowWidget extends StatelessWidget {
SizedBox(height: 6),
if (!isTestnet)
Text(
'${secondAvailableFiatBalance}',
secondAvailableFiatBalance,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
@@ -464,7 +470,7 @@ class BalanceRowWidget extends StatelessWidget {
children: [
SizedBox(height: 24),
Text(
'${secondAdditionalBalanceLabel}',
secondAdditionalBalanceLabel,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
@@ -500,31 +506,15 @@ class BalanceRowWidget extends StatelessWidget {
),
IntrinsicHeight(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegin,
label: depositToL2Label,
child: OutlinedButton(
onPressed: () {
final mwebAddress =
bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((mwebAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${mwebAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
},
onPressed: () => depositToL2(context),
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
side: BorderSide(
@@ -537,7 +527,7 @@ class BalanceRowWidget extends StatelessWidget {
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -549,7 +539,7 @@ class BalanceRowWidget extends StatelessWidget {
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegin,
depositToL2Label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.w700,
@@ -564,25 +554,9 @@ class BalanceRowWidget extends StatelessWidget {
SizedBox(width: 16),
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegout,
label: withdrawFromL2Label,
child: OutlinedButton(
onPressed: () {
final litecoinAddress =
bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.mweb,
},
);
},
onPressed: () => withdrawFromL2(context),
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surface,
side: BorderSide(
@@ -607,7 +581,7 @@ class BalanceRowWidget extends StatelessWidget {
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegout,
withdrawFromL2Label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context)
.colorScheme
@@ -625,7 +599,7 @@ class BalanceRowWidget extends StatelessWidget {
),
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
],
),
),
@@ -635,20 +609,74 @@ class BalanceRowWidget extends StatelessWidget {
);
}
// double getShadowSpread(){
// double spread = 3;
// else if (!dashboardViewModel.settingsStore.currentTheme.isDark) spread = 3;
// else if (dashboardViewModel.settingsStore.currentTheme.isDark) spread = 1;
// return spread;
// }
//
//
// double getShadowBlur(){
// double blur = 7;
// else if (dashboardViewModel.settingsStore.currentTheme.isDark) blur = 7;
// else if (dashboardViewModel.settingsStore.currentTheme.isDark) blur = 3;
// return blur;
// }
String get depositToL2Label => dashboardViewModel.type == WalletType.litecoin
? S.current.litecoin_mweb_pegin
: S.current.bitcoin_lightning_deposit;
String get withdrawFromL2Label => dashboardViewModel.type == WalletType.litecoin
? S.current.litecoin_mweb_pegout
: S.current.bitcoin_lightning_withdraw;
Future<void> depositToL2(BuildContext context) async {
PaymentRequest? paymentRequest = null;
if (dashboardViewModel.type == WalletType.litecoin) {
final depositAddress = bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
if ((depositAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$depositAddress"));
}
} else if (dashboardViewModel.type == WalletType.bitcoin) {
final depositAddress = await bitcoin!.getUnusedSpakDepositAddress(dashboardViewModel.wallet);
if ((depositAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$depositAddress"));
}
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
}
Future<void> withdrawFromL2(BuildContext context) async {
PaymentRequest? paymentRequest = null;
UnspentCoinType unspentCoinType = UnspentCoinType.any;
final withdrawAddress = bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
if (dashboardViewModel.type == WalletType.litecoin) {
if ((withdrawAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("litecoin:$withdrawAddress"));
}
unspentCoinType = UnspentCoinType.mweb;
} else if (dashboardViewModel.type == WalletType.bitcoin) {
if ((withdrawAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(Uri.parse("bitcoin:$withdrawAddress"));
}
unspentCoinType = UnspentCoinType.lightning;
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': unspentCoinType,
},
);
}
void onPressedHelp() {
var helpUri = Uri.parse("https://docs.cakewallet.com/cryptos/bitcoin#lightning");
if (dashboardViewModel.type == WalletType.litecoin) {
helpUri = Uri.parse("https://docs.cakewallet.com/cryptos/litecoin#mweb");
}
launchUrl(helpUri, mode: LaunchMode.externalApplication);
}
void _showBalanceDescription(BuildContext context, String content) {
showPopUp<void>(context: context, builder: (_) => InformationPage(information: content));
@@ -1,9 +1,12 @@
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/apps_widget.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@@ -12,6 +15,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
class CakeFeaturesPage extends StatelessWidget {
CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel});
@@ -21,112 +25,195 @@ class CakeFeaturesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 24, top: 16),
child: Text(
S.of(context).apps,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Expanded(
child: ListView(
children: <Widget>[
SizedBox(height: 2),
DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () {
if (Platform.isMacOS) {
_launchUrl("buy.cakepay.com");
} else {
_navigatorToGiftCardsPage(context);
}
},
title: 'Cake Pay',
subTitle: S.of(context).cake_pay_subtitle,
image: Image.asset(
'assets/images/cakepay.png',
height: 74,
width: 70,
fit: BoxFit.cover,
),
),
Observer(builder: (_) {
if (dashboardViewModel.wallet.chainId == 1) {
return DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => Navigator.of(context).pushNamed(Routes.dEuroSavings),
title: S.of(context).deuro_savings,
subTitle: S.of(context).deuro_savings_subtitle,
image: Image.asset(
'assets/images/deuro_icon.png',
height: 80,
width: 80,
fit: BoxFit.cover,
return GradientBackground(
scaffold: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(context),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: !FeatureFlag.hasNewUi ? _buildOldUi(context) : _buildNewUi(context),
),
),
);
}
return const SizedBox();
}),
DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => _launchUrl("cake.nano-gpt.com"),
title: "NanoGPT",
subTitle: S.of(context).nanogpt_subtitle,
image: Image.asset(
'assets/images/nanogpt.png',
height: 80,
width: 80,
fit: BoxFit.cover,
),
},
),
SizedBox(height: 125),
],
),
),
],
),
],
),
);
}
Widget _buildHeader(BuildContext context) {
if (!FeatureFlag.hasNewUi) {
return Padding(
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16),
child: Text(
S.of(context).apps,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
);
}
return Align(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: Text(
S.of(context).apps,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
);
}
Widget _buildOldUi(BuildContext context) {
return Column(
children: [
const SizedBox(height: 2),
DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => _onCakePayTap(context),
title: 'Cake Pay',
subTitle: S.of(context).cake_pay_subtitle,
image: Image.asset('assets/images/cakepay.png', height: 74, width: 70, fit: BoxFit.cover),
),
Observer(builder: (_) {
if (dashboardViewModel.type == WalletType.ethereum) {
return DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => Navigator.of(context).pushNamed(Routes.dEuroSavings),
title: S.of(context).deuro_savings,
subTitle: S.of(context).deuro_savings_subtitle,
image: Image.asset('assets/images/deuro_icon.png', height: 80, width: 80, fit: BoxFit.cover),
);
}
return const SizedBox();
}),
DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () => _launchUrl("cake.nano-gpt.com"),
title: "NanoGPT",
subTitle: S.of(context).nanogpt_subtitle,
image: Image.asset('assets/images/nanogpt.png', height: 80, width: 80, fit: BoxFit.cover),
),
const Spacer(),
const SizedBox(height: 125),
],
);
}
Widget _buildNewUi(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 24, bottom: 8),
child: CakeImageWidget(imageUrl: "assets/new-ui/by-cakelabs.svg", height: 20, color: Theme.of(context).colorScheme.onSurfaceVariant),
),
),
AppsWidget(
isWide: true,
isCake: true,
onTap: () => _onCakePayTap(context),
title: 'Cake Pay',
subTitle: S.of(context).cake_pay_subtitle,
image: 'assets/images/cakepay.png',
),
AppsWidget(
isWide: true,
isLink: true,
isCake: true,
onTap: () => _launchUrl("cupcakewallet.com"),
title: "Cupcake",
subTitle: "Turn your old phone into your new hardware wallet with our new app",
image: 'assets/images/cupcake.png',
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 8),
child: Text(
"Featured Apps",
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
AppsWidget(
isWide: true,
isLink: true,
onTap: () => _launchUrl("cake.nano-gpt.com"),
title: "NanoGPT",
subTitle: S.of(context).nanogpt_subtitle,
image: 'assets/images/nanogpt.png',
),
Observer(builder: (_) {
if (dashboardViewModel.type == WalletType.ethereum) {
return AppsWidget(
isWide: true,
onTap: () => Navigator.of(context).pushNamed(Routes.dEuroSavings),
title: S.of(context).deuro_savings,
subTitle: S.of(context).deuro_savings_subtitle,
image: 'assets/images/deuro_icon.png',
);
}
return const SizedBox();
}),
const Spacer(),
const SizedBox(height: 125),
],
);
}
void _onCakePayTap(BuildContext context) {
if (Platform.isMacOS) {
_launchUrl("buy.cakepay.com");
} else {
_navigatorToGiftCardsPage(context);
}
}
void _launchUrl(String url) {
try {
launchUrl(
Uri.https(url),
mode: LaunchMode.externalApplication,
);
launchUrl(Uri.https(url), mode: LaunchMode.externalApplication);
} catch (e) {
printV(e);
}
}
void _navigatorToGiftCardsPage(BuildContext context) {
final walletType = dashboardViewModel.type;
switch (walletType) {
case WalletType.haven:
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).gift_cards_unavailable,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
break;
default:
Navigator.pushNamed(context, Routes.cakePayCardsPage);
if (dashboardViewModel.type == WalletType.haven) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).gift_cards_unavailable,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
} else {
Navigator.pushNamed(context, Routes.cakePayCardsPage);
}
}
}
}
@@ -36,23 +36,24 @@ class NFTDetailsPage extends BasePage {
@override
Widget trailing(BuildContext context) {
final menuButton = Image.asset(
'assets/images/menu.png',
color: Theme.of(context).colorScheme.onSurface,
);
return Container(
alignment: Alignment.centerRight,
width: 40,
child: TextButton(
// FIX-ME: Style
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () => onOpenEndDrawer(),
child: Semantics(label: S.of(context).wallet_menu, child: menuButton),
),
);
return SizedBox.shrink();
// final menuButton = Image.asset(
// 'assets/images/menu.png',
// color: Theme.of(context).colorScheme.onSurface,
// );
//
// return Container(
// alignment: Alignment.centerRight,
// width: 40,
// child: TextButton(
// // FIX-ME: Style
// //highlightColor: Colors.transparent,
// //splashColor: Colors.transparent,
// //padding: EdgeInsets.all(0),
// onPressed: () => onOpenEndDrawer(),
// child: Semantics(label: S.of(context).wallet_menu, child: menuButton),
// ),
// );
}
@override
@@ -139,7 +139,7 @@ class _ImportNFTPage extends BasePage {
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () async {
await nftViewModel.importNFT(tokenAddressController.text, tokenIDController.text);
Navigator.pop(context);
if (context.mounted && Navigator.of(context).canPop()) Navigator.pop(context);
},
);
}),
@@ -50,20 +50,16 @@ class _NFTListingPageState extends State<NFTListingPage> {
),
),
if (widget.nftViewModel.isLoading)
Expanded(
child: Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
)
else
Expanded(
child: NFTListWidget(nftViewModel: widget.nftViewModel),
),
NFTListWidget(nftViewModel: widget.nftViewModel)
],
);
},
@@ -95,26 +91,38 @@ class NFTListWidget extends StatelessWidget {
if (isSolana) {
if (nftViewModel.solanaNftAssetModels.isEmpty) return emptyMessage;
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 16),
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: nftViewModel.solanaNftAssetModels.length,
itemBuilder: (context, index) {
final nftAsset = nftViewModel.solanaNftAssetModels[index];
return SolanaNFTTileWidget(nftAsset: nftAsset);
},
return Padding(
padding: EdgeInsets.only(
bottom: 64),
child: ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 16),
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: nftViewModel.solanaNftAssetModels.length,
itemBuilder: (context, index) {
final nftAsset = nftViewModel.solanaNftAssetModels[index];
return SolanaNFTTileWidget(nftAsset: nftAsset);
},
),
);
} else {
if (nftViewModel.nftAssetByWalletModels.isEmpty) return emptyMessage;
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 16),
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: nftViewModel.nftAssetByWalletModels.length,
itemBuilder: (context, index) {
final nftAsset = nftViewModel.nftAssetByWalletModels[index];
return NFTTileWidget(nftAsset: nftAsset);
},
return Padding(
padding: EdgeInsets.only(
bottom: 64),
child: ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 16),
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: nftViewModel.nftAssetByWalletModels.length,
itemBuilder: (context, index) {
final nftAsset = nftViewModel.nftAssetByWalletModels[index];
return NFTTileWidget(nftAsset: nftAsset);
},
),
);
}
},
@@ -155,8 +155,8 @@ class TransactionsPage extends StatelessWidget {
),
currency: "BTC",
state: item.status,
amount: bitcoin!
.formatterBitcoinAmountToString(amount: session.amount.toInt()),
amount: dashboardViewModel.appStore.amountParsingProxy
.getDisplayCryptoString(session.amount.toInt(), CryptoCurrency.btc),
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
isSending: session.isSenderSession,
);
@@ -173,18 +173,22 @@ class TransactionsPage extends StatelessWidget {
return tradeFrom != null && tradeTo != null
? Observer(
builder: (_) => TradeRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
swapState: trade.state,
provider: trade.provider,
from: tradeFrom,
to: tradeTo,
createdAtFormattedDate: trade.createdAt != null
? DateFormat('HH:mm').format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount,
formattedReceiveAmount: item.tradeFormattedReceiveAmount),
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
swapState: trade.state,
provider: trade.provider,
title: "$tradeFrom → $tradeTo",
fromSymbol: dashboardViewModel.appStore.amountParsingProxy
.getCryptoSymbol(tradeFrom),
toSymbol: dashboardViewModel.appStore.amountParsingProxy
.getCryptoSymbol(tradeTo),
createdAtFormattedDate: trade.createdAt != null
? DateFormat("HH:mm").format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount,
formattedReceiveAmount: item.tradeFormattedReceiveAmount,
),
)
: Container();
}
+109 -109
View File
@@ -1,133 +1,133 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sign_form.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/verify_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class SignPage extends BasePage {
SignPage(this.signViewModel)
: signFormKey = GlobalKey<SignFormState>(),
verifyFormKey = GlobalKey<VerifyFormState>(),
_pages = [],
_controller = PageController(initialPage: 0) {
class SignPage extends StatefulWidget {
SignPage(this.signViewModel);
final SignViewModel signViewModel;
@override
State<SignPage> createState() => _SignPageState();
}
class _SignPageState extends State<SignPage> {
List<Widget> _pages = [];
final signFormKey = GlobalKey<SignFormState>();
final verifyFormKey = GlobalKey<VerifyFormState>();
final PageController _controller = PageController(initialPage: 0) ;
bool _isEffectsInstalled = false;
void initState() {
super.initState();
_pages.add(SignForm(
key: signFormKey,
type: signViewModel.wallet.type,
chainId: signViewModel.wallet.chainId,
includeAddress: signViewModel.signIncludesAddress,
type: widget.signViewModel.wallet.type,
chainId: widget.signViewModel.wallet.chainId,
includeAddress: widget.signViewModel.signIncludesAddress,
));
_pages.add(VerifyForm(
key: verifyFormKey,
type: signViewModel.wallet.type,
chainId: signViewModel.wallet.chainId,
type: widget.signViewModel.wallet.type,
chainId: widget.signViewModel.wallet.chainId,
));
}
@override
Widget middle(BuildContext context) => Observer(
builder: (_) {
return Text(
S.current.sign_verify_title,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
);
},
);
final SignViewModel signViewModel;
final PageController _controller;
final List<Widget> _pages;
final GlobalKey<SignFormState> signFormKey;
final GlobalKey<VerifyFormState> verifyFormKey;
bool _isEffectsInstalled = false;
@override
Widget body(BuildContext context) {
Widget build(BuildContext context) {
_setEffects(context);
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).colorScheme.surfaceVariant,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: FocusNode(),
toolbarButtons: [(_) => KeyboardDoneButton()],
)
],
),
child: Container(
height: 0,
color: Theme.of(context).colorScheme.surface,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: PageView.builder(
onPageChanged: (page) {
signViewModel.isSigning = page == 0;
},
controller: _controller,
itemCount: _pages.length,
itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]),
),
),
if (_pages.length > 1)
Padding(
padding: EdgeInsets.only(top: 10),
child: SmoothPageIndicator(
controller: _controller,
count: _pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).colorScheme.outline.withOpacity(0.5),
activeDotColor: Theme.of(context).colorScheme.outline,
return Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
top:false,
child: Column(
children: [
ModalTopBar(title: S.current.sign_verify_title,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator
.of(context)
.pop,),
Expanded(
child: KeyboardHideOverlay(
child: Container(
height: 0,
color: Theme.of(context).colorScheme.surface,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: PageView.builder(
onPageChanged: (page) {
widget.signViewModel.isSigning = page == 0;
},
controller: _controller,
itemCount: _pages.length,
itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]),
),
),
if (_pages.length > 1)
Padding(
padding: EdgeInsets.only(top: 10),
child: SmoothPageIndicator(
controller: _controller,
count: _pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).colorScheme.outline.withOpacity(0.5),
activeDotColor: Theme.of(context).colorScheme.outline,
),
),
),
Padding(
padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24),
child: Column(
children: [
Observer(
builder: (context) {
return LoadingPrimaryButton(
onPressed: () async {
await _confirmForm(context);
},
text: widget.signViewModel.isSigning
? S.current.sign_message
: S.current.verify_message,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: widget.signViewModel.state is IsExecutingState,
isDisabled: widget.signViewModel.state is IsExecutingState,
);
},
),
],
),
)
],
),
),
),
Padding(
padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24),
child: Column(
children: [
Observer(
builder: (context) {
return LoadingPrimaryButton(
onPressed: () async {
await _confirmForm(context);
},
text: signViewModel.isSigning
? S.current.sign_message
: S.current.verify_message,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: signViewModel.state is IsExecutingState,
isDisabled: signViewModel.state is IsExecutingState,
);
},
),
],
),
)
],
),
),
),
],
),
),
);
@@ -139,7 +139,7 @@ class SignPage extends BasePage {
}
_isEffectsInstalled = true;
reaction((_) => signViewModel.state, (ExecutionState state) {
reaction((_) => widget.signViewModel.state, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
@@ -150,14 +150,14 @@ class SignPage extends BasePage {
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () {
if (Navigator.canPop(context)) Navigator.of(context).pop();
if (context.mounted && Navigator.canPop(context)) Navigator.of(context).pop();
},
);
});
});
}
if (state is ExecutedSuccessfullyState) {
if (signViewModel.isSigning) {
if (widget.signViewModel.isSigning) {
signFormKey.currentState!.signatureController.text = state.payload as String;
} else {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -182,18 +182,18 @@ class SignPage extends BasePage {
Future<void> _confirmForm(BuildContext context) async {
FocusManager.instance.primaryFocus?.unfocus();
if (signViewModel.isSigning) {
if (widget.signViewModel.isSigning) {
String message = signFormKey.currentState!.messageController.text;
String? address;
if (signViewModel.signIncludesAddress) {
if (widget.signViewModel.signIncludesAddress) {
address = signFormKey.currentState!.addressController.text;
}
await signViewModel.sign(message, address: address);
await widget.signViewModel.sign(message, address: address);
} else {
String message = verifyFormKey.currentState!.messageController.text;
String signature = verifyFormKey.currentState!.signatureController.text;
String address = verifyFormKey.currentState!.addressController.text;
await signViewModel.verify(message, signature, address: address);
await widget.signViewModel.verify(message, signature, address: address);
}
}
}
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ActionButton extends StatelessWidget {
ActionButton({
@@ -0,0 +1,314 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:cake_wallet/entities/new_main_actions.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
class NewMainNavBar extends StatefulWidget {
const NewMainNavBar({
super.key,
required this.dashboardViewModel,
required this.selectedIndex,
required this.onItemTap,
});
final DashboardViewModel dashboardViewModel;
final int selectedIndex;
final Function(int index) onItemTap;
static const barHeight = 68.0;
static final barBottomPadding = Platform.isIOS ? 22.0 : 12.0;
@override
State<NewMainNavBar> createState() => _NEWNewMainNavBarState();
}
class _NEWNewMainNavBarState extends State<NewMainNavBar> {
static const iconBoxWidth = 48.0;
static const iconWidth = 28.0;
static const iconHeight = 28.0;
static const iconHorizontalPadding = 18.0;
static const pillIconWidth = 24.0;
static const pillIconHeight = 24.0;
static const pillIconSpacing = 18.0;
static const pillHorizontalPadding = 20.0;
static const barBorderRadius = 50.0;
static const pillBorderRadius = 50.0;
static const barHorizontalPadding = 6.0;
static const barResizeDuration = Duration(milliseconds: 300);
static const inactiveIconMoveDuration = Duration(milliseconds: 300);
static const inactiveIconFadeDuration = Duration(milliseconds: 300);
static const inactiveIconAppearDuration = Duration(milliseconds: 300);
static const pillMoveDuration = Duration(milliseconds: 250);
static const pillResizeDuration = Duration(milliseconds: 250);
static const iconColorChangeDuration = Duration(milliseconds: 200);
static const pillTextStyle = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
);
bool _firstFrame = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
setState(() => _firstFrame = false);
});
}
void _onItemTap(int index) {
// if (index == widget.selectedIndex) return;
//
// setState(() {
// widget.selectedIndex = index;
// });
widget.onItemTap(index);
NewMainActions.all[index].onTap.call();
}
// Estimate pill width based on text length
double _estimatePillWidthForAction(
BuildContext context, NewMainActions action,
{Color? color}) {
final text = action.name(context);
final textPainter = TextPainter(
text: TextSpan(
text: text,
style: pillTextStyle.copyWith(color: color),
),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout();
return pillIconWidth +
pillIconSpacing +
textPainter.width +
pillHorizontalPadding;
}
double calcLeft(int index, double pillWidth) {
final baseOffset = iconBoxWidth * index;
double additionalSpacing;
if (index > widget.selectedIndex)
additionalSpacing = pillWidth - iconBoxWidth;
else
additionalSpacing = 0;
return baseOffset + additionalSpacing;
}
double calcBarWidth(double pillWidth, int visibleActionsLength) {
return (iconWidth + iconHorizontalPadding) * visibleActionsLength + (pillWidth - iconWidth) + barHorizontalPadding + pillIconSpacing / double.infinity - 2;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final backgroundColor = theme.colorScheme.surfaceContainer.withAlpha(127);
final pillColor = theme.colorScheme.onSurface.withAlpha(25);
final activeColor = theme.colorScheme.onSurface;
final inactiveColor = theme.colorScheme.primary;
final visibleActions = NewMainActions.all
.where(
(action) => action.canShow?.call(widget.dashboardViewModel) ?? true)
.toList();
final pillWidth = _estimatePillWidthForAction(
context, visibleActions[widget.selectedIndex],
color: activeColor);
final barWidth = calcBarWidth(pillWidth, visibleActions.length);
final currentAction = visibleActions[widget.selectedIndex];
return Align(
alignment: Alignment.bottomCenter,
child: SafeArea(
bottom: !(Platform.isIOS),
top: false,
child: Padding(
// tux PLEASE consult me (malik) before removing this padding.
padding: EdgeInsets.only(bottom: NewMainNavBar.barBottomPadding),
child: AnimatedContainer(
duration: barResizeDuration,
curve: Curves.easeOutCubic,
width: barWidth,
child: ClipRSuperellipse(
borderRadius: BorderRadius.circular(barBorderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: Container(
height: NewMainNavBar.barHeight,
decoration: ShapeDecoration(
color: backgroundColor,
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(barBorderRadius),
side: const BorderSide(color: Color(0x14FFFFFF), width: 1),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: barHorizontalPadding),
child: Stack(
alignment: Alignment.center,
children: [
AnimatedPill(
left: calcLeft(widget.selectedIndex, pillWidth),
pillColor: pillColor,
currentAction: currentAction,
pillIconHeight: pillIconHeight,
pillIconWidth: pillIconWidth,
pillIconSpacing: pillIconSpacing,
pillBorderRadius: pillBorderRadius,
contentColor: activeColor,
estimateWidthForAction: pillWidth,
pillTextStyle: pillTextStyle,
pillMoveDuration: pillMoveDuration,
pillResizeDuration: pillResizeDuration,
),
for (int i = 0; i < visibleActions.length; i++)
AnimatedPositioned(
duration: pillResizeDuration,
width: iconBoxWidth,
left: calcLeft(i, pillWidth)+((i == widget.selectedIndex) ? iconHorizontalPadding/100 : 0),
curve: Curves.easeOutCubic,
child: InkWell(
splashFactory: NoSplash.splashFactory,
splashColor: Colors.transparent,
borderRadius: BorderRadius.circular(pillBorderRadius),
onTap: () => _onItemTap(i),
child: AnimatedContainer(
duration: _firstFrame
? Duration.zero
: inactiveIconMoveDuration,
curve: Curves.easeOutCubic,
width:
i == widget.selectedIndex ? pillWidth : iconBoxWidth,
alignment: Alignment.center,
child: AnimatedAlign(
duration: inactiveIconFadeDuration,
curve: Curves.easeOutCubic,
alignment: Alignment.center,
child: AnimatedScale(
duration: inactiveIconAppearDuration,
curve: Curves.easeOutCubic,
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
child: TweenAnimationBuilder<Color?>(
tween: ColorTween(
begin: (i == widget.selectedIndex) ? inactiveColor : activeColor,
end: (i==widget.selectedIndex) ? activeColor : inactiveColor,
),
duration: iconColorChangeDuration,
builder: (context, value, child) {
return Container(
height: NewMainNavBar.barHeight,
child: SvgPicture.asset(
visibleActions[i].image,
width: iconWidth,
height: iconHeight,
//fit: BoxFit.scaleDown,
colorFilter: ColorFilter.mode(
value ?? inactiveColor,
BlendMode.srcIn,
),
),
);
}
),
),
),
),
),
),
],
),
)),
),
),
),
),
),
);
}
}
class AnimatedPill extends StatelessWidget {
const AnimatedPill({
super.key,
required this.left,
required this.pillColor,
required this.contentColor,
required this.pillIconHeight,
required this.pillIconWidth,
required this.pillBorderRadius,
required this.currentAction,
required this.estimateWidthForAction,
required this.pillIconSpacing,
required this.pillTextStyle,
required this.pillMoveDuration,
required this.pillResizeDuration,
});
final double left;
final Color pillColor;
final Color contentColor;
final double pillIconHeight;
final double pillIconWidth;
final double pillBorderRadius;
final NewMainActions currentAction;
final double estimateWidthForAction;
final double pillIconSpacing;
final TextStyle pillTextStyle;
final Duration pillMoveDuration;
final Duration pillResizeDuration;
@override
Widget build(BuildContext context) {
return AnimatedPositioned(
duration: pillMoveDuration,
curve: Curves.easeOutCubic,
left: left,
top: 6,
bottom: 6,
child: AnimatedContainer(
duration: pillResizeDuration,
curve: Curves.easeOutCubic,
width: estimateWidthForAction,
decoration: ShapeDecoration(
color: pillColor,
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(pillBorderRadius)),
),
clipBehavior: Clip.hardEdge,
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(padding: EdgeInsets.only(left: pillIconWidth + 2),
child: Text(
currentAction.name(context),
style: pillTextStyle.copyWith(color: contentColor),
overflow: TextOverflow.fade,
softWrap: false,
),
),
],
),
),
));
}
}
@@ -25,15 +25,13 @@ class SignForm extends StatefulWidget {
class SignFormState extends State<SignForm> with AutomaticKeepAliveClientMixin {
SignFormState()
: formKey = GlobalKey<FormState>(),
messageController = TextEditingController(),
: messageController = TextEditingController(),
addressController = TextEditingController(),
signatureController = TextEditingController();
final TextEditingController messageController;
final TextEditingController addressController;
final TextEditingController signatureController;
final GlobalKey<FormState> formKey;
@override
void initState() {
@@ -55,35 +53,33 @@ class SignFormState extends State<SignForm> with AutomaticKeepAliveClientMixin {
padding: EdgeInsets.only(left: 24, right: 24),
child: Column(
children: [
Form(
key: formKey,
child: Column(
children: [
AddressTextField(
controller: messageController,
placeholder: S.current.message,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
),
if (widget.includeAddress) ...[
const SizedBox(height: 20),
AddressTextField(
controller: addressController,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.walletAddresses
],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
fillColor: Theme.of(context).colorScheme.surface,
),
Column(
children: [
AddressTextField(
controller: messageController,
placeholder: S.current.message,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
),
if (widget.includeAddress) ...[
const SizedBox(height: 20),
AddressTextField(
controller: addressController,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.walletAddresses
],
],
)),
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
fillColor: Theme.of(context).colorScheme.surface,
),
],
],
),
const SizedBox(height: 20),
GestureDetector(
onTap: () async {
@@ -1,15 +1,15 @@
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cw_core/crypto_currency.dart';
import "package:cake_wallet/exchange/trade_state.dart";
import "package:cake_wallet/palette.dart";
import "package:cake_wallet/utils/image_utill.dart";
import "package:flutter/material.dart";
import "package:cake_wallet/exchange/exchange_provider_description.dart";
class TradeRow extends StatelessWidget {
TradeRow({
required this.provider,
required this.from,
required this.to,
required this.title,
required this.fromSymbol,
required this.toSymbol,
required this.createdAtFormattedDate,
this.onTap,
this.formattedAmount,
@@ -20,8 +20,9 @@ class TradeRow extends StatelessWidget {
final VoidCallback? onTap;
final ExchangeProviderDescription provider;
final CryptoCurrency from;
final CryptoCurrency to;
final String fromSymbol;
final String toSymbol;
final String title;
final String? createdAtFormattedDate;
final String? formattedAmount;
final String? formattedReceiveAmount;
@@ -29,9 +30,6 @@ class TradeRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final amountCrypto = from.toString();
final receiveAmountCrypto = to.toString();
return InkWell(
onTap: onTap,
child: Container(
@@ -73,7 +71,7 @@ class TradeRow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'${from.toString()} → ${to.toString()}',
title,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
@@ -81,7 +79,7 @@ class TradeRow extends StatelessWidget {
),
formattedAmount != null
? Text(
formattedAmount! + ' ' + amountCrypto,
"$formattedAmount $fromSymbol",
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
@@ -106,7 +104,7 @@ class TradeRow extends StatelessWidget {
: Container(),
formattedReceiveAmount != null
? Text(
formattedReceiveAmount! + ' ' + receiveAmountCrypto,
"$formattedReceiveAmount $toSymbol",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
@@ -20,15 +20,13 @@ class VerifyForm extends StatefulWidget {
class VerifyFormState extends State<VerifyForm> with AutomaticKeepAliveClientMixin {
VerifyFormState()
: formKey = GlobalKey<FormState>(),
messageController = TextEditingController(),
: messageController = TextEditingController(),
addressController = TextEditingController(),
signatureController = TextEditingController();
final TextEditingController messageController;
final TextEditingController addressController;
final TextEditingController signatureController;
final GlobalKey<FormState> formKey;
@override
void initState() {
@@ -48,38 +46,35 @@ class VerifyFormState extends State<VerifyForm> with AutomaticKeepAliveClientMix
super.build(context);
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: Form(
key: formKey,
child: Column(
children: [
AddressTextField(
controller: messageController,
placeholder: S.current.message,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
),
const SizedBox(height: 20),
AddressTextField(
controller: addressController,
options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
),
const SizedBox(height: 20),
AddressTextField(
controller: signatureController,
placeholder: S.current.signature,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
),
],
),
child: Column(
children: [
AddressTextField(
controller: messageController,
placeholder: S.current.message,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
),
const SizedBox(height: 20),
AddressTextField(
controller: addressController,
options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
),
const SizedBox(height: 20),
AddressTextField(
controller: signatureController,
placeholder: S.current.signature,
options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
fillColor: Theme.of(context).colorScheme.surface,
),
],
),
);
}
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:intl/intl.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class DevExchangeProviderLogsPage extends BasePage {
final ExchangeProviderLogsViewModel viewModel;
@@ -266,6 +267,7 @@ class _LogsList extends StatelessWidget {
Widget build(BuildContext context) {
return Observer(
builder: (_) => ListView.builder(
controller: ModalScrollController.of(context),
itemCount: viewModel.filteredLogs.length,
itemBuilder: (context, index) {
final log = viewModel.filteredLogs[index];
@@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class DisclaimerPage extends BasePage {
DisclaimerPage({this.isReadOnly = false});
@@ -59,6 +60,8 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
child: Stack(
children: <Widget>[
SingleChildScrollView(
controller: ModalScrollController.of(context),
physics: ClampingScrollPhysics(),
padding: EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
+6 -2
View File
@@ -32,7 +32,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.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/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/core/address_validator.dart';
@@ -615,7 +615,7 @@ class ExchangePage extends BasePage {
try {
exchangeViewModel.receiveCurrency =
CryptoCurrency.fromString(initialPaymentRequest!.scheme);
exchangeViewModel.receiveAmount = initialPaymentRequest!.amount;
exchangeViewModel.setCanonicalReceiveAmount(initialPaymentRequest!.amount);
exchangeViewModel.receiveAddress = initialPaymentRequest!.address;
} catch (e) {
printV('error: ${e.toString()}');
@@ -718,6 +718,7 @@ class ExchangePage extends BasePage {
? AmountValidator(
isAutovalidate: true,
currency: exchangeViewModel.depositCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
minValue: exchangeViewModel.limits.min.toString(),
maxValue: exchangeViewModel.limits.max.toString(),
).call(value)
@@ -734,6 +735,7 @@ class ExchangePage extends BasePage {
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
},
useSatoshis: exchangeViewModel.useSatoshiDeposit,
),
);
@@ -765,6 +767,7 @@ class ExchangePage extends BasePage {
? AmountValidator(
isAutovalidate: true,
currency: exchangeViewModel.receiveCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
minValue: exchangeViewModel.limits.min.toString(),
maxValue: exchangeViewModel.limits.max.toString(),
).call(value)
@@ -781,6 +784,7 @@ class ExchangePage extends BasePage {
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
},
useSatoshis: exchangeViewModel.useSatoshisReceive,
),
);
@@ -9,7 +9,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.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/exchange/exchange_view_model.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
@@ -129,8 +129,10 @@ class ExchangeTemplatePage extends BasePage {
Theme.of(context).colorScheme.surfaceContainerHighest,
borderColor: Theme.of(context).colorScheme.outlineVariant,
fillColor: Theme.of(context).colorScheme.surfaceContainer,
currencyValueValidator:
AmountValidator(currency: exchangeViewModel.depositCurrency),
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.depositCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
),
),
),
),
@@ -160,8 +162,10 @@ class ExchangeTemplatePage extends BasePage {
Theme.of(context).colorScheme.surfaceContainerHighest,
borderColor: Theme.of(context).colorScheme.outlineVariant,
fillColor: Theme.of(context).colorScheme.surfaceContainerLow,
currencyValueValidator:
AmountValidator(currency: exchangeViewModel.receiveCurrency),
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.receiveCurrency,
amountParsingProxy: exchangeViewModel.amountParsingProxy,
),
),
),
)
@@ -11,7 +11,6 @@ class CurrencyPicker extends StatefulWidget {
required this.onItemSelected,
this.title,
this.hintText,
this.isMoneroWallet = false,
this.isConvertFrom = false,
super.key});
@@ -19,7 +18,6 @@ class CurrencyPicker extends StatefulWidget {
final List<Currency> items;
final String? title;
final Function(Currency) onItemSelected;
final bool isMoneroWallet;
final bool isConvertFrom;
final String? hintText;
@@ -47,6 +47,7 @@ class ExchangeCard<T extends Currency> extends StatefulWidget {
this.onPushPasteButton,
this.onPushAddressBookButton,
this.onDispose,
this.useSatoshis = false,
required this.cardInstanceName,
}) : super(key: key);
@@ -82,6 +83,7 @@ class ExchangeCard<T extends Currency> extends StatefulWidget {
final Function()? onDispose;
final String cardInstanceName;
final Color fillColor;
final bool useSatoshis;
@override
ExchangeCardState<T> createState() => ExchangeCardState<T>();
@@ -224,8 +226,8 @@ class ExchangeCardState<T extends Currency> extends State<ExchangeCard<T>> {
currencyAmountTextFieldWidgetKey:
ValueKey('${_cardInstanceName}_currency_amount_textfield_widget_key'),
imageArrow: widget.imageArrow,
selectedCurrency: _selectedCurrency.toString(),
selectedCurrencyDecimals: _selectedCurrency.decimals,
selectedCurrency: widget.useSatoshis ? "SATS" : "$_selectedCurrency",
selectedCurrencyDecimals: widget.useSatoshis ? 0 : _selectedCurrency.decimals,
amountFocusNode: widget.amountFocusNode,
amountController: amountController,
onTapPicker: () => _presentPicker(context),
@@ -383,7 +385,7 @@ class ExchangeCardState<T extends Currency> extends State<ExchangeCard<T>> {
onTap: () async {
final contact = await Navigator.of(context).pushNamed(
Routes.pickerAddressBook,
arguments: widget.initialCurrency,
arguments: [widget.initialCurrency, true],
);
if (contact is ContactBase) {
@@ -452,7 +454,6 @@ class ExchangeCardState<T extends Currency> extends State<ExchangeCard<T>> {
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
items: widget.currencies,
hintText: S.of(context).search_currency,
isMoneroWallet: _isMoneroWallet,
isConvertFrom: widget.hasRefundAddress,
onItemSelected: (Currency item) => widget.onCurrencySelected(item as T),
),
@@ -86,6 +86,7 @@ class _BuySellOptionButtonsState extends State<BuySellOptionButtons> {
Expanded(
flex: 5,
child: SelectButton(
center:true,
height: 44,
text: S.of(context).buy,
isSelected: isBuySelected,
@@ -107,6 +108,7 @@ class _BuySellOptionButtonsState extends State<BuySellOptionButtons> {
Expanded(
flex: 5,
child: SelectButton(
center:true,
height: 44,
text: S.of(context).sell,
isSelected: !isBuySelected,
@@ -11,7 +11,7 @@ import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/src/screens/base_page.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';
class ExchangeTradeExternalSendPage extends BasePage {
ExchangeTradeExternalSendPage({required this.exchangeTradeViewModel});
@@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
@@ -10,6 +11,7 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_shee
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
@@ -22,7 +24,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/widgets/timer_widget.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/src/widgets/alert_with_one_action.dart';
void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) {
@@ -176,8 +178,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
textColor: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.onPrimary,
isDisabled: widget.exchangeTradeViewModel.isSwapsXyzSendingEVMTokenSwap,
),
SizedBox(height: 16),
Observer(
builder: (_) {
@@ -293,49 +295,54 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final trade = widget.exchangeTradeViewModel.trade;
final isSwapsXyz = trade.provider == ExchangeProviderDescription.swapsXyz;
final isEVMWallet = widget.exchangeTradeViewModel.sendViewModel.isEVMWallet;
final amountValue = isSwapsXyz && isEVMWallet
final amountValue = isSwapsXyz && isEVMWallet && !widget.exchangeTradeViewModel.isSwapsXYZCanSendFromExternal
? trade.amount
: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.amountFormatted;
final fiatAmountValue = isSwapsXyz && isEVMWallet && !widget.exchangeTradeViewModel.isSwapsXYZCanSendFromExternal
? widget.exchangeTradeViewModel.sendViewModel.calculateTransactionFiatAmount(amountValue)
: widget.exchangeTradeViewModel.sendViewModel.pendingTransactionFiatAmountFormatted;
if (context.mounted) {
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) {
builder: (bottomSheetContext) {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
return ConfirmSendingBottomSheet(
key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
footerType: FooterType.slideActionButton,
isSlideActionEnabled:
widget.exchangeTradeViewModel.sendViewModel.isReadyForSend,
walletType: widget.exchangeTradeViewModel.sendViewModel.walletType,
isSlideActionEnabled: sendVM.isReadyForSend,
walletType: sendVM.walletType,
titleText: S.of(bottomSheetContext).confirm_transaction,
titleIconPath:
widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
titleIconPath: sendVM.selectedCryptoCurrency.iconPath,
currency: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue: amountValue,
fiatAmountValue: widget
.exchangeTradeViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
fee:
isEVMCompatibleChain(widget.exchangeTradeViewModel.sendViewModel.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue: widget
.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted,
feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel
.pendingTransactionFeeFiatAmountFormatted,
outputs: widget.exchangeTradeViewModel.sendViewModel.outputs,
amountValue: sendVM.amountParsingProxy
.getDisplayCryptoAmount(amountValue, sendVM.selectedCryptoCurrency),
fiatAmountValue: fiatAmountValue,
fee: isEVMCompatibleChain(sendVM.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue: "${sendVM.amountParsingProxy.getDisplayCryptoAmount(
sendVM.pendingTransaction!.feeFormattedValue,
sendVM.selectedCryptoCurrency)} ${sendVM.amountParsingProxy
.getCryptoSymbol(sendVM.wallet.currency)}",
feeFiatAmount: sendVM.pendingTransactionFeeFiatAmountFormatted,
outputs: sendVM.outputs,
onSlideActionComplete: () async {
if (bottomSheetContext.mounted && Navigator.canPop(bottomSheetContext)) {
Navigator.of(bottomSheetContext).pop(true);
}
widget.exchangeTradeViewModel.sendViewModel.commitTransaction(context);
if (bottomSheetContext.mounted && Navigator.canPop(bottomSheetContext)) Navigator.of(bottomSheetContext).pop(true);
sendVM.commitTransaction(context);
widget.exchangeTradeViewModel.registerSwapsXyzTransaction();
},
amountParsingProxy: sendVM.amountParsingProxy,
);
},
);
@@ -217,7 +217,7 @@ class FeeSelectionWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${output.estimatedFee} ${feesViewModel.currency}',
'${output.estimatedFee} ${feesViewModel.amountParsingProxy.getCryptoSymbol(feesViewModel.currency)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
@@ -14,7 +14,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
class NanoChangeRepPage extends BasePage {
NanoChangeRepPage({required SettingsStore settingsStore, required WalletBase wallet})
@@ -12,7 +12,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.
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/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@@ -84,7 +84,7 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBody> {
final TextEditingController passphraseController = TextEditingController();
final TextEditingController confirmPassphraseController = TextEditingController();
final _formKey = GlobalKey<FormState>();
final _formKey = GlobalKey<NodeFormState>();
final _passphraseFormKey = GlobalKey<FormState>();
bool? testnetValue;
@@ -274,7 +274,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 24),
child: NodeForm(
formKey: _formKey,
key: _formKey,
nodeViewModel: widget.nodeViewModel,
),
)
@@ -1,6 +1,6 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cw_core/generate_name.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
@@ -13,7 +13,7 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/picker.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/src/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -203,7 +203,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
borderRadius: BorderRadius.circular(12.0),
color: Theme.of(context).colorScheme.surface,
),
width: 34,
@@ -267,7 +267,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
child: SelectButton(
key: ValueKey('new_wallet_page_monero_seed_type_button_key'),
text: widget._seedSettingsViewModel.moneroSeedType.title,
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(18),
onTap: () async {
await showPopUp<void>(
context: context,
@@ -291,7 +291,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
builder: (BuildContext build) => Padding(
padding: EdgeInsets.only(top: 10),
child: SeedLanguageSelector(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(18),
key: _languageSelectorKey,
buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'),
initialSelected: defaultSeedLanguage,
@@ -9,7 +9,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.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/src/widgets/search_bar_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -37,19 +37,19 @@ class NewWalletTypePage extends BasePage {
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget body(BuildContext context) => WalletTypeForm(
walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
isCreate: newWalletTypeArguments.isCreate,
onTypeSelected: newWalletTypeArguments.onTypeSelected,
hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
);
walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
isCreate: newWalletTypeArguments.isCreate,
onTypeSelected: newWalletTypeArguments.onTypeSelected,
hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
);
}
class WalletTypeForm extends StatefulWidget {
@@ -86,10 +86,10 @@ class WalletTypeFormState extends State<WalletTypeForm> {
void initState() {
types = filteredTypes = availableWalletTypes
.where((element) =>
!widget.isHardwareWallet ||
DeviceConnectionType.supportedConnectionTypes(
element, widget.hardwareWalletType!, Platform.isIOS)
.isNotEmpty)
!widget.isHardwareWallet ||
DeviceConnectionType.supportedConnectionTypes(
element, widget.hardwareWalletType!, Platform.isIOS)
.isNotEmpty)
.toList();
super.initState();
@@ -116,8 +116,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
S.of(context).choose_wallet_currency,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
Padding(
@@ -132,12 +132,12 @@ class WalletTypeFormState extends State<WalletTypeForm> {
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
...filteredTypes.map(
(type) => Padding(
(type) => Padding(
padding: EdgeInsets.only(top: 12),
child: SelectButton(
key: ValueKey('new_wallet_type_${type.name}_button_key'),
image: Image.asset(
getCryptoCurrencyForWalletListItem(type).iconPath ?? '',
getCryptoCurrencyIconForWalletListItem(type),
height: 24,
width: 24,
),
@@ -148,7 +148,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
onTap: () => setState(() => selected = type),
deviceConnectionTypes: widget.isHardwareWallet
? DeviceConnectionType.supportedConnectionTypes(
type, widget.hardwareWalletType!, Platform.isIOS)
type, widget.hardwareWalletType!, Platform.isIOS)
: [],
),
),
@@ -201,4 +201,4 @@ class WalletTypeFormState extends State<WalletTypeForm> {
Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected!);
}
}
}
}
@@ -95,7 +95,7 @@ class WalletGroupsDisplayBody extends StatelessWidget {
isSelected:
walletGroupsDisplayViewModel.selectedSingleWallet == wallet,
leadingWidget: Image.asset(
getCryptoCurrencyForWalletListItem(wallet.type).iconPath!,
getCryptoCurrencyIconForWalletListItem(wallet.type),
width: 32,
height: 32,
),
@@ -146,14 +146,16 @@ class WalletGroupsDisplayBody extends StatelessWidget {
Future<void> onTypeSelected(BuildContext context) async {
final mnemonic = await walletGroupsDisplayViewModel.getSelectedWalletMnemonic();
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(
type: walletGroupsDisplayViewModel.type,
mnemonic: mnemonic,
isChildWallet: true,
),
);
if (context.mounted) {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(
type: walletGroupsDisplayViewModel.type,
mnemonic: mnemonic,
isChildWallet: true,
),
);
}
}
}
@@ -67,8 +67,8 @@ class GroupedWalletExpansionTile extends StatelessWidget {
return Padding(
padding: EdgeInsets.symmetric(vertical: 6),
child: ExpansionTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
collapsedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18)),
collapsedShape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18)),
collapsedBackgroundColor: backgroundColor,
backgroundColor: backgroundColor,
onExpansionChanged: onExpansionChanged,
@@ -80,7 +80,13 @@ class GroupedWalletExpansionTile extends StatelessWidget {
EdgeInsets.symmetric(vertical: 1, horizontal: !isCurrentlySelectedWallet ? 16 : 0),
iconColor: effectiveArrowColor,
collapsedIconColor: effectiveArrowColor,
leading: leadingWidget,
leading: (childWallets.isEmpty && onTitleTapped != null && leadingWidget != null)
? GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTitleTapped,
child: leadingWidget,
)
: leadingWidget,
trailing: trailingWidget ?? (childWallets.isEmpty ? SizedBox.shrink() : null),
title: GestureDetector(
onTap: onTitleTapped,
@@ -123,7 +129,7 @@ class GroupedWalletExpansionTile extends StatelessWidget {
: SizedBox(width: 7),
SizedBox(width: 24),
Image.asset(
getCryptoCurrencyForWalletListItem(item.type).iconPath!,
getCryptoCurrencyIconForWalletListItem(item.type),
width: 32,
height: 32,
),
@@ -17,7 +17,7 @@ class SelectButton extends StatelessWidget {
this.deviceConnectionTypes,
this.borderRadius,
this.padding,
super.key,
super.key, this.center = false,
});
final Widget? image;
@@ -34,6 +34,7 @@ class SelectButton extends StatelessWidget {
final Color? borderColor;
final BorderRadius? borderRadius;
final EdgeInsets? padding;
final bool center;
@override
Widget build(BuildContext context) {
@@ -64,10 +65,9 @@ class SelectButton extends StatelessWidget {
child: Container(
width: double.infinity,
height: height,
padding: padding ?? EdgeInsets.only(left: 30, right: 30),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(15)),
borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(18)),
color: backgroundColor,
border: borderColor != null ? Border.all(color: borderColor!, width: 2) : null,
),
@@ -78,12 +78,13 @@ class SelectButton extends StatelessWidget {
children: <Widget>[
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: center ? MainAxisAlignment.center : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
if(!center) SizedBox(width:16),
image ?? Offstage(),
Padding(
padding: image != null ? EdgeInsets.only(left: 15) : EdgeInsets.only(left: 0),
padding: image != null ? EdgeInsets.only(left: 14) : EdgeInsets.only(left: 0),
child: Text(
text,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
@@ -96,7 +97,8 @@ class SelectButton extends StatelessWidget {
],
),
),
...trailingIcons
...trailingIcons,
if(!center) SizedBox(width:16),
],
),
),
@@ -1,53 +1,51 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.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/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/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cw_core/node.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:mobx/mobx.dart';
class NodeCreateOrEditPage extends BasePage {
class NodeCreateOrEditPage extends StatefulWidget {
NodeCreateOrEditPage({
required this.nodeCreateOrEditViewModel,
this.editingNode,
this.isSelected,
this.type,
}) : _formKey = GlobalKey<FormState>();
});
final GlobalKey<FormState> _formKey;
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final Node? editingNode;
final bool? isSelected;
final WalletType? type;
@override
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
State<NodeCreateOrEditPage> createState() => _NodeCreateOrEditPageState();
}
class _NodeCreateOrEditPageState extends State<NodeCreateOrEditPage> {
final _nodeFormKey = GlobalKey<NodeFormState>();
@override
Widget trailing(BuildContext context) => IconButton(
onPressed: () => nodeCreateOrEditViewModel.scanQRCodeForNewNode(context),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset('assets/images/qr_code_icon.png'),
);
@override
Widget body(BuildContext context) {
void initState() {
super.initState();
reaction(
(_) => nodeCreateOrEditViewModel.connectionState,
(ExecutionState state) {
(_) => widget.nodeCreateOrEditViewModel.connectionState,
(ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => showPopUp<void>(
(_) => showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).new_node_testing,
@@ -63,7 +61,7 @@ class NodeCreateOrEditPage extends BasePage {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => showPopUp<void>(
(_) => showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).error,
@@ -77,78 +75,104 @@ class NodeCreateOrEditPage extends BasePage {
},
);
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: const EdgeInsets.only(bottom: 24.0, top: 8),
content: NodeForm(
formKey: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
type: type,
),
bottomSectionPadding: const EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: Column(
children: [
ModalTopBar(
title: widget.editingNode != null ? S.current.edit_node : S.current.node_new,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
trailingIcon: SvgPicture.asset(
"assets/new-ui/scan.svg",width:24,height:24,
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
),
onTrailingPressed: () => widget.nodeCreateOrEditViewModel.scanQRCodeForNewNode(context),
),
Expanded(
child: KeyboardHideOverlay(
child: Container(
padding: const EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (context) => AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent: S.of(context).remove_node_message,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false),
padding: const EdgeInsets.only(left: 18, right: 18),
child: ScrollableWithBottomSection(
contentPadding: const EdgeInsets.only(bottom: 24.0, top: 8),
content: NodeForm(
key: _nodeFormKey,
nodeViewModel: widget.nodeCreateOrEditViewModel,
),
bottomSectionPadding: const EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: const EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (context) => AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent: S.of(context).remove_node_message,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false),
),
) ??
false;
if (confirmed) {
await widget.editingNode!.delete();
Navigator.of(context).pop();
}
},
text: S.of(context).delete,
isDisabled: widget.editingNode == null ||
!widget.nodeCreateOrEditViewModel.isReady ||
(widget.isSelected ?? false),
color: Theme.of(context).colorScheme.errorContainer,
textColor: Theme.of(context).colorScheme.onErrorContainer,
),
),
) ??
false;
if (confirmed) {
await editingNode!.delete();
Navigator.of(context).pop();
}
},
text: S.of(context).delete,
isDisabled: editingNode == null ||
!nodeCreateOrEditViewModel.isReady ||
(isSelected ?? false),
color: Theme.of(context).colorScheme.errorContainer,
textColor: Theme.of(context).colorScheme.onErrorContainer,
),
Flexible(
child: Container(
padding: const EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) {
return;
}
await widget.nodeCreateOrEditViewModel.save(
editingNode: widget.editingNode, saveAsCurrent: widget.isSelected ?? false);
if (context.mounted) {
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: (!widget.nodeCreateOrEditViewModel.isReady) ||
(widget.nodeCreateOrEditViewModel.connectionState is IsExecutingState),
),
),
),
],
),
),
),
),
),
Flexible(
child: Container(
padding: const EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nodeCreateOrEditViewModel.save(
editingNode: editingNode, saveAsCurrent: isSelected ?? false);
if (context.mounted) {
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: (!nodeCreateOrEditViewModel.isReady) ||
(nodeCreateOrEditViewModel.connectionState is IsExecutingState),
),
),
),
],
),
),
],
),
),
);
@@ -11,13 +11,12 @@ import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
class PowNodeCreateOrEditPage extends BasePage {
PowNodeCreateOrEditPage(
{required this.nodeCreateOrEditViewModel, this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
: _addressController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
@@ -55,7 +54,6 @@ class PowNodeCreateOrEditPage extends BasePage {
.addListener(() => nodeCreateOrEditViewModel.password = _passwordController.text);
}
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _portController;
final TextEditingController _loginController;
@@ -80,6 +78,7 @@ class PowNodeCreateOrEditPage extends BasePage {
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final Node? editingNode;
final bool? isSelected;
final _formKey = GlobalKey<NodeFormState>();
@override
Widget body(BuildContext context) {
@@ -118,9 +117,8 @@ class PowNodeCreateOrEditPage extends BasePage {
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0, top: 8),
content: NodeForm(
formKey: _formKey,
key: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
+175 -235
View File
@@ -1,270 +1,210 @@
import 'package:cake_wallet/core/node_address_validator.dart';
import 'package:cake_wallet/core/node_port_validator.dart';
import 'package:cake_wallet/core/socks_proxy_node_address_validator.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_Item_checkbox.dart';
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/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:mobx/mobx.dart';
class NodeForm extends StatefulWidget {
NodeForm({
super.key,
required this.nodeViewModel,
required this.formKey,
this.editingNode,
this.type,
});
final NodeCreateOrEditViewModel nodeViewModel;
final GlobalKey<FormState> formKey;
final Node? editingNode;
final WalletType? type;
@override
State<StatefulWidget> createState() =>
_NodeFormState(nodeViewModel: nodeViewModel, editingNode: this.editingNode);
State<StatefulWidget> createState() => NodeFormState(vm: nodeViewModel);
}
class _NodeFormState extends State<NodeForm> {
_NodeFormState({
required this.nodeViewModel,
Node? editingNode,
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_pathController = TextEditingController(text: editingNode?.path.toString()),
_portController = TextEditingController(
text: (editingNode != null && editingNode.uri.hasPort)
? editingNode.uri.port.toString()
: ''),
_loginController = TextEditingController(text: editingNode?.login),
_passwordController = TextEditingController(text: editingNode?.password),
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress) {
if (editingNode != null) {
nodeViewModel
..setAddress(editingNode.uri.host.toString())
..setPath(editingNode.path.toString())
..setPort(editingNode.uri.hasPort ? editingNode.uri.port.toString() : '')
..setPassword(editingNode.password ?? '')
..setLogin(editingNode.login ?? '')
..setSSL(editingNode.isSSL)
..setTrusted(editingNode.trusted)
..setIsEnabledForAutoSwitching(editingNode.isEnabledForAutoSwitching)
..setSocksProxy(editingNode.useSocksProxy)
..setSocksProxyAddress(editingNode.socksProxyAddress ?? '');
}
class NodeFormState extends State<NodeForm> {
NodeFormState({required this.vm});
if (nodeViewModel.hasAuthCredentials) {
reaction((_) => nodeViewModel.login, (String login) {
if (login != _loginController.text) _loginController.text = login;
});
reaction((_) => nodeViewModel.password, (String password) {
if (password != _passwordController.text) _passwordController.text = password;
});
}
reaction((_) => nodeViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeViewModel.port, (String port) {
if (port != _portController.text) _portController.text = port;
});
reaction((_) => nodeViewModel.path, (String path) {
if (path != _pathController.text) _pathController.text = path;
});
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
_pathController.addListener(() => nodeViewModel.path = _pathController.text);
_portController.addListener(() => nodeViewModel.port = _portController.text);
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
_socksAddressController
.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
}
final NodeCreateOrEditViewModel nodeViewModel;
final TextEditingController _addressController;
final TextEditingController _pathController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
final TextEditingController _socksAddressController;
final Map<String, TextEditingController> _controllers = {};
@override
Widget build(BuildContext context) => Form(
key: widget.formKey,
child: Column(children: <Widget>[
Row(children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _addressController,
hintText: S.of(context).node_address,
validator: widget.type == WalletType.decred
void initState() {
super.initState();
for(final key in vm.textFieldKeys) {
_controllers[key] = TextEditingController();
_controllers[key]!.text = vm.getTextValue(key);
_controllers[key]!.addListener(() {
final text = _controllers[key]!.text;
vm.updateViewModelFromText(key, text);
});
}
_setupReactions();
}
@override
void dispose() {
for (final controller in _controllers.values) {
controller.dispose();
}
super.dispose();
}
final NodeCreateOrEditViewModel vm;
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Observer(
builder: (_)=>Form(
key: _formKey,
child: NewListSections(
sections: {
'main': [
ListItemTextField(
keyValue: vm.nodeLabelUIKey,
label: 'Node label',
initialValue: vm.label,
),
ListItemTextField(
keyValue: vm.nodeAddressUIKey,
label: S.current.node_address,
initialValue: vm.address,
validator: vm.walletType == WalletType.decred
? NodeAddressValidatorDecredBlankException()
: NodeAddressValidator(),
),
)
]),
const SizedBox(height: 10),
if (nodeViewModel.hasPathSupport) ...[
Row(children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _pathController,
hintText: "/path",
if (vm.hasPathSupport)
ListItemTextField(
keyValue: vm.nodePathUIKey,
label: '/path',
initialValue: vm.path,
validator: NodePathValidator(),
),
)
]),
const SizedBox(height: 10),
],
Row(children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _portController,
hintText: S.of(context).node_port,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
ListItemTextField(
keyValue: vm.nodePortUIKey,
label: S.current.node_port,
initialValue: '',
//keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
validator: NodePortValidator(),
),
)
]),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.useSSL,
borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
iconColor: Theme.of(context).colorScheme.primary,
onChanged: (value) => nodeViewModel.useSSL = value,
caption: S.of(context).use_ssl,
),
)
],
),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.isEnabledForAutoSwitching,
borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
iconColor: Theme.of(context).colorScheme.primary,
onChanged: (value) => nodeViewModel.isEnabledForAutoSwitching = value,
caption: S.current.enable_for_auto_switching,
),
if (vm.hasAuthCredentials) ...[
ListItemTextField(
keyValue: vm.nodeUsernameUIKey,
label: S.current.login,
initialValue: vm.login,
),
],
),
),
const SizedBox(height: 10),
if (nodeViewModel.hasAuthCredentials) ...[
Row(children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _loginController,
hintText: S.of(context).login,
ListItemTextField(
keyValue: vm.nodePasswordUIKey,
label: S.current.password,
initialValue: vm.password,
),
)
]),
const SizedBox(height: 10),
Row(children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _passwordController,
hintText: S.of(context).password,
),
)
]),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.trusted,
borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
iconColor: Theme.of(context).colorScheme.primary,
onChanged: (value) => nodeViewModel.trusted = value,
caption: S.of(context).trusted,
),
),
],
]
],
'advanced': [
ListItemCheckbox(
keyValue: vm.useSSLUIKey,
label: S.current.use_ssl,
value: vm.useSSL,
onChanged: (value) => vm.useSSL = value),
ListItemCheckbox(
keyValue: vm.nodeTrustedUIKey,
label: S.current.trusted,
value: vm.trusted,
onChanged: (value) => vm.trusted = value,
),
),
Observer(
builder: (_) => Column(children: [
if (nodeViewModel.usesEmbeddedProxy) ...[
Padding(
padding: const EdgeInsets.only(top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.usesEmbeddedProxy,
gradientBackground: false,
borderColor: Theme.of(context).dividerColor,
iconColor: Theme.of(context).colorScheme.primary,
onChanged: null,
caption: 'Embedded Tor SOCKS Proxy',
),
],
),
),
],
Padding(
padding: const EdgeInsets.only(top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
iconColor: Theme.of(context).colorScheme.primary,
onChanged: (value) {
if (!value) _socksAddressController.text = '';
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
if (vm.usesEmbeddedProxy)
ListItemCheckbox(
keyValue: vm.nodeEmbeddedTorProxyUIKey,
label: 'Embedded Tor SOCKS Proxy',
value: vm.usesEmbeddedProxy,
onChanged: (_) {},
),
if (nodeViewModel.useSocksProxy) ...[
const SizedBox(height: 10),
Row(children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _socksAddressController,
hintText: '[<ip>:]<port>',
validator: SocksProxyNodeAddressValidator(),
),
)
]),
]
]),
),
]
]),
ListItemCheckbox(
keyValue: vm.useSocksProxyUIKey,
label: 'Use SOCKS Proxy',
value: vm.useSocksProxy,
onChanged: (value) {
vm.socksProxyAddress = '';
vm.useSocksProxy = value;
},
),
if (vm.useSocksProxy)
ListItemTextField(
keyValue: vm.socksProxyAddressUIKey,
label: 'host:port',
initialValue: vm.socksProxyAddress,
validator: SocksProxyNodeAddressValidator(),
),
ListItemCheckbox(
keyValue: vm.autoSwitchingUIKey,
label: S.current.enable_for_auto_switching,
value: vm.isEnabledForAutoSwitching,
onChanged: (value) => vm.isEnabledForAutoSwitching = value),
],
},
controllers: _controllers,
getCheckboxValue: vm.getCheckboxValue,
updateCheckboxValue: vm.updateCheckboxValue,
tapHandlers: tapHandlers
),
),
);
}
Map<String, VoidCallback> get tapHandlers => {
'node_regular_with_drill_in_row_key': () => _showToast('Regular with drill-in row tapped'),
'node_tall_row_key': () => _showToast('Tall row tapped'),
'node_regular_with_trailing_row_key': () => _showToast('Regular with trailing row tapped'),
'node_item_selector_row_key': () => _showToast('Item selector row tapped'),
};
void _showToast(String msg) async {
try {
await Fluttertoast.showToast(
msg: msg,
backgroundColor: Color.fromRGBO(0, 0, 0, 0.85),
);
} catch (_) {}
}
void _setupReactions() {
_bindController(() => vm.label, vm.nodeLabelUIKey);
_bindController(() => vm.address, vm.nodeAddressUIKey);
_bindController(() => vm.port, vm.nodePortUIKey);
_bindController(() => vm.path, vm.nodePathUIKey);
if (vm.hasAuthCredentials) {
_bindController(() => vm.login, vm.nodeUsernameUIKey);
_bindController(() => vm.password, vm.nodePasswordUIKey);
}
if (vm.hasPathSupport) {
_bindController(() => vm.path, vm.nodePathUIKey);
}
_bindController(
() => vm.socksProxyAddress,
vm.socksProxyAddressUIKey,
);
}
void _bindController<T>(
T Function() observe,
String uiKey,
) {
reaction<T>((_) => observe(), (value) {
final controller = _controllers[uiKey];
if (controller != null && controller.text != value.toString()) {
controller.text = value.toString();
}
});
}
bool validate() => _formKey.currentState?.validate() ?? false;
}
@@ -21,24 +21,26 @@ class NodeListRow extends StandardListRow {
final leading = buildLeading(context);
final trailing = buildTrailing(context);
return Container(
height: 56,
padding: EdgeInsets.only(left: 12, right: 12, top: 2, bottom: 2),
padding: EdgeInsets.only(left: 18, right: 18, top: 2, bottom: 2),
margin: EdgeInsets.only(top: 2, bottom: 2),
child: FilledButton(
onPressed: () => onTap?.call(context),
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
leading,
buildCenter(context, hasLeftOffset: true),
trailing,
],
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
leading,
buildCenter(context, hasLeftOffset: true),
trailing,
],
),
),
),
);
@@ -1,3 +1,4 @@
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:another_flushbar/flushbar.dart';
@@ -141,7 +142,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
},
child: Container(
color: Theme.of(context).colorScheme.surface,
padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 60.0),
padding: EdgeInsets.only(left: 44.0, right: 44.0, bottom: 60.0),
child: Column(
children: <Widget>[
Spacer(flex: 2),
@@ -153,7 +154,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
color: Theme.of(context).colorScheme.onSurface,
),
),
Spacer(flex: 3),
Spacer(flex: 2),
Container(
width: 180,
child: Row(
@@ -206,11 +207,11 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
childAspectRatio: _aspectRatio,
childAspectRatio: DeviceInfo.instance.isDesktop ? _aspectRatio : 1.1,
physics: const NeverScrollableScrollPhysics(),
children: List.generate(12, (index) {
const double marginRight = 8;
const double marginLeft = 8;
const double marginRight = 10;
const double marginLeft = 10;
if (index == 9) {
// Empty container
@@ -228,7 +229,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
button: true,
onTap: () => _pop(),
child: TextButton(
onPressed: () => _pop(),
onPressed: () => {_pop(), HapticFeedback.mediumImpact()},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
shape: CircleBorder(),
@@ -246,7 +247,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
margin: EdgeInsets.only(left: marginLeft, right: marginRight),
child: TextButton(
key: ValueKey('pin_code_button_${index}_key'),
onPressed: () => _push(index),
onPressed: () => {_push(index), HapticFeedback.mediumImpact()},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
shape: CircleBorder(),
+26 -12
View File
@@ -1,11 +1,13 @@
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class AddressListPage extends BasePage {
class AddressListPage extends StatelessWidget {
AddressListPage(this.addressListViewModel);
final WalletAddressListViewModel addressListViewModel;
@@ -14,18 +16,30 @@ class AddressListPage extends BasePage {
String get title => S.current.accounts_subaddresses;
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
AddressList(
addressListViewModel: addressListViewModel,
onSelect: (String address) async {
Navigator.of(context).pop(address);
},
Widget build(BuildContext context) {
return Column(
children: [
ModalTopBar(
title: S.of(context).accounts_subaddresses,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
child: Column(
children: <Widget>[
AddressList(
addressListViewModel: addressListViewModel,
onSelect: (String address) async {
Navigator.of(context).pop(address);
},
),
],
),
),
],
),
),
],
);
}
}
@@ -4,6 +4,7 @@ import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
@@ -21,7 +22,7 @@ import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/generated/i18n.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:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -53,10 +54,13 @@ class AnonPayInvoicePage extends BasePage {
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
AppBarStyle get appBarStyle =>
FeatureFlag.hasNewUiExtraPages ? AppBarStyle.completelyTransparent : AppBarStyle.transparent;
@override
void onClose(BuildContext context) => Navigator.popUntil(context, (route) => route.isFirst);
void onClose(BuildContext context) => FeatureFlag.hasNewUiExtraPages
? Navigator.of(context, rootNavigator: true).pop()
: Navigator.popUntil(context, (route) => route.isFirst);
@override
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
@@ -1,8 +1,8 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
class CopyLinkItem extends StatelessWidget {
const CopyLinkItem({super.key, required this.url, required this.title});
@@ -40,7 +40,7 @@ class CopyLinkItem extends StatelessWidget {
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => Share.share(url),
onPressed: () => ShareUtil.share(text: url, context: context),
icon: Icon(
Icons.share,
color: Theme.of(context).colorScheme.primary,
@@ -184,7 +184,7 @@ class CurrencyAmountTextField extends StatelessWidget {
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')),
],
hintText: hintText ?? '0.0000',
hintText: hintText ?? (selectedCurrencyDecimals == 0 ? '0' : '0.0000'),
fillColor: fillColor,
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
@@ -198,8 +198,13 @@ class CurrencyAmountTextField extends StatelessWidget {
),
validator: isAmountEditable ? currencyValueValidator : null,
onChanged: (value) {
final sanitized =
var sanitized =
value.replaceAll(',', '.').withMaxDecimals(selectedCurrencyDecimals);
if (selectedCurrencyDecimals == 0) {
sanitized = sanitized.replaceAll('.', '');
}
if (sanitized != amountController.text) {
// Update text while preserving a sane cursor position to avoid auto-selection
amountController.value = amountController.value.copyWith(
@@ -1,4 +1,3 @@
import 'package:cake_wallet/core/payment_uris.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/routes.dart';
@@ -15,6 +14,7 @@ import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.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/payment_uris.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -108,6 +108,7 @@ class QRWidget extends StatelessWidget {
),
if (addressListViewModel.isPayjoinUnavailable &&
!addressListViewModel.isSilentPayments &&
!addressListViewModel.isLightning &&
!addressListViewModel.isBitcoinViewOnly) ...[
GestureDetector(
onTap: () => _onPayjoinInactivePressed(context),
@@ -155,8 +156,7 @@ class QRWidget extends StatelessWidget {
),
),
],
if (addressListViewModel.payjoinEndpoint.isNotEmpty &&
!addressListViewModel.isSilentPayments) ...[
if (addressListViewModel.isPayjoinAvailable) ...[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -204,7 +204,7 @@ class QRWidget extends StatelessWidget {
borderWidth: 0.0,
selectedCurrency: _currencyName,
selectedCurrencyDecimals:
addressListViewModel.selectedCurrency.decimals,
addressListViewModel.selectedCurrencyDecimals,
amountFocusNode: amountTextFieldFocusNode,
amountController: amountController,
padding: EdgeInsets.only(top: 20, left: _width / 4),
@@ -255,8 +255,7 @@ class QRWidget extends StatelessWidget {
),
Observer(
builder: (_) => Offstage(
offstage: addressListViewModel.payjoinEndpoint.isEmpty ||
addressListViewModel.isSilentPayments,
offstage: !addressListViewModel.isPayjoinAvailable,
child: Padding(
padding: EdgeInsets.only(top: 12),
child: PrimaryImageButton(
@@ -283,6 +282,7 @@ class QRWidget extends StatelessWidget {
String get _currencyName {
if (addressListViewModel.selectedCurrency is CryptoCurrency) {
if (addressListViewModel.useSatoshi) return "SATS";
return (addressListViewModel.selectedCurrency as CryptoCurrency).title.toUpperCase();
}
return addressListViewModel.selectedCurrency.name.toUpperCase();
+65 -34
View File
@@ -1,66 +1,76 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class RescanPage extends BasePage {
RescanPage(this._rescanViewModel)
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
class RescanPage extends StatefulWidget {
RescanPage(this._rescanViewModel);
@override
String get title =>
_rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
final RescanViewModel _rescanViewModel;
@override
Widget body(BuildContext context) {
State<RescanPage> createState() => _RescanPageState();
}
class _RescanPageState extends State<RescanPage> {
final TextEditingController _heightController = TextEditingController();
@override
Widget build(BuildContext context) {
Widget child;
if (_rescanViewModel.wallet.type != WalletType.decred) {
if (widget._rescanViewModel.wallet.type != WalletType.decred) {
child = Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Observer(
builder: (_) => BlockchainHeightWidget(
key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
isMwebScan: _rescanViewModel.isMwebScan,
doSingleScan: _rescanViewModel.doSingleScan,
hasDatePicker: !_rescanViewModel.isMwebScan, // disable date picker for mweb for now
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type,
bitcoinMempoolAPIEnabled: _rescanViewModel.isBitcoinMempoolAPIEnabled,
builder: (_) => SingleChildScrollView(
controller: ModalScrollController.of(context),
child: BlockchainHeightWidget(
// key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) => widget._rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: widget._rescanViewModel.isSilentPaymentsScan,
isMwebScan: widget._rescanViewModel.isMwebScan,
doSingleScan: widget._rescanViewModel.doSingleScan,
hasDatePicker: !widget._rescanViewModel.isMwebScan,
// disable date picker for mweb for now
toggleSingleScan: () =>
widget._rescanViewModel.doSingleScan = !widget._rescanViewModel.doSingleScan,
walletType: widget._rescanViewModel.wallet.type,
heightController: _heightController,
bitcoinMempoolAPIEnabled: widget._rescanViewModel.isBitcoinMempoolAPIEnabled,
),
),
),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
isLoading: widget._rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
if (_rescanViewModel.isSilentPaymentsScan) {
if (widget._rescanViewModel.isSilentPaymentsScan) {
return _toggleSilentPaymentsScanning(context);
}
_rescanViewModel.rescanCurrentWallet(
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
widget._rescanViewModel.rescanCurrentWallet(
restoreHeight: int.tryParse(_heightController.text) ?? 0);
Navigator.of(context).pop();
},
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: !_rescanViewModel.isButtonEnabled,
isDisabled: !widget._rescanViewModel.isButtonEnabled,
),
)
],
@@ -76,10 +86,10 @@ class RescanPage extends BasePage {
Spacer(),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
isLoading: widget._rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
await _rescanViewModel.rescanCurrentWallet(restoreHeight: 0);
await widget._rescanViewModel.rescanCurrentWallet(restoreHeight: 0);
Navigator.of(context).pop();
},
color: Theme.of(context).colorScheme.primary,
@@ -93,18 +103,39 @@ class RescanPage extends BasePage {
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).unfocus(),
child: child,
// onTap: () => FocusScope.of(context).unfocus(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: KeyboardHideOverlay(
child: SafeArea(
child: Column(
children: [
ModalTopBar(
title: widget._rescanViewModel.isSilentPaymentsScan
? S.current.silent_payments_scanning
: S.current.rescan,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop),
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: child,
)),
],
),
),
),
),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final height = _blockchainHeightWidgetKey.currentState!.height;
final height = int.parse(_heightController.text);
Navigator.of(context).pop();
final needsToSwitch =
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
await bitcoin!.getNodeIsElectrsSPEnabled(widget._rescanViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
@@ -117,13 +148,13 @@ class RescanPage extends BasePage {
actionRightButton: () async {
Navigator.of(_dialogContext).pop();
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
widget._rescanViewModel.rescanCurrentWallet(restoreHeight: height);
},
actionLeftButton: () => Navigator.of(_dialogContext).pop(),
),
);
}
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
widget._rescanViewModel.rescanCurrentWallet(restoreHeight: height);
}
}
@@ -23,9 +23,9 @@ class RestoreOptionsPage extends BasePage {
@override
Widget body(BuildContext context) => _RestoreOptionsBody(
isNewInstall: isNewInstall,
themeType: currentTheme.type,
);
isNewInstall: isNewInstall,
themeType: currentTheme.type,
);
}
class _RestoreOptionsBody extends StatefulWidget {
@@ -93,7 +93,6 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
image: imageRestoreCupcake,
title: S.of(context).restore_title_from_cupcake,
description: S.of(context).restore_description_from_cupcake,
tag: S.of(context).new_tag,
),
),
Padding(
@@ -144,21 +143,21 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.error,
alertContent: error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.error,
alertContent: error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
});
}
Future<void> _onScanQRCode(BuildContext context) async {
final isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
try {
@@ -181,4 +180,4 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
_showQRScanError(context, e.toString());
}
}
}
}
@@ -7,7 +7,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cw_core/generate_name.dart';
import 'package:flutter/services.dart';
class WalletRestoreFromKeysForm extends StatefulWidget {
@@ -1,5 +1,5 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cw_core/generate_name.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
@@ -171,7 +171,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
borderRadius: BorderRadius.circular(12.0),
color: Theme.of(context).colorScheme.surface,
),
width: 34,
@@ -490,12 +490,10 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
controller: _tabController,
children: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: _buildWalletRestoreFromSeedTab(),
),
if (_hasKeysTab)
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: _buildWalletRestoreFromKeysTab(),
),
],
@@ -32,6 +32,7 @@ class SeedVerificationPage extends BasePage {
walletSeedViewModel.verificationIndices.isEmpty
? SeedVerificationSuccessView(
key: ValueKey('seed_verification_success_view_page'),
walletType: walletSeedViewModel.walletType,
currentTheme: currentTheme,
)
: SeedVerificationStepView(
@@ -1,13 +1,17 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class SeedVerificationSuccessView extends StatelessWidget {
const SeedVerificationSuccessView({required this.currentTheme, super.key});
const SeedVerificationSuccessView(
{required this.currentTheme, super.key, required this.walletType});
final MaterialThemeBase currentTheme;
final WalletType walletType;
@override
Widget build(BuildContext context) {
@@ -18,7 +22,7 @@ class SeedVerificationSuccessView extends StatelessWidget {
ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
child: CakeImageWidget(
height: 200,
height: 128,
imageUrl: currentTheme.isDark
? 'assets/images/seed_verified_dark.png'
: 'assets/images/seed_verified_light.png',
@@ -62,9 +66,13 @@ class SeedVerificationSuccessView extends StatelessWidget {
PrimaryButton(
key: ValueKey('wallet_seed_page_open_wallet_button_key'),
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
if (walletType == WalletType.bitcoin) {
Navigator.of(context).pushNamed(Routes.lightningUsernamePage, arguments: true);
} else {
Navigator.of(context).popUntil((route) => route.isFirst);
}
},
text: S.current.open_wallet,
text: (walletType == WalletType.bitcoin) ? S.current.continue_text : S.current.open_wallet,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
+4 -2
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/warning_box_widget.dart';
import 'package:cake_wallet/utils/clipboard_util.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -20,7 +21,7 @@ class WalletSeedPage extends BasePage {
WalletSeedPage(this.walletSeedViewModel, {required this.isNewWalletCreated});
@override
String get title => '${walletSeedViewModel.walletType} ${S.current.seed_title}';
String get title => '${walletTypeToString(walletSeedViewModel.walletType)} ${S.current.seed_title}';
final bool isNewWalletCreated;
final WalletSeedViewModel walletSeedViewModel;
@@ -57,7 +58,8 @@ class WalletSeedPage extends BasePage {
@override
Widget body(BuildContext context) {
return PopScope(
canPop: false,
//FIXME i can't figure out why this was here? it caused back gesture to break
canPop: true,
child: Container(
alignment: Alignment.center,
child: Column(
+1 -1
View File
@@ -2,7 +2,7 @@ import 'package:cake_wallet/core/selectable_option.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/provider_optoin_tile.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/themes/core/material_base_theme.dart';
import 'package:flutter/material.dart';
+91 -108
View File
@@ -1,13 +1,13 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
@@ -22,7 +22,7 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_shee
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.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/src/widgets/simple_checkbox.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
@@ -32,12 +32,12 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
@@ -98,8 +98,7 @@ class SendPage extends BasePage {
size: 16,
);
final _closeButton = currentTheme.isDark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
final isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
return MergeSemantics(
child: SizedBox(
@@ -150,27 +149,25 @@ class SendPage extends BasePage {
@override
Widget trailing(context) => Observer(
builder: (_) {
return sendViewModel.isBatchSending
? TrailButton(
caption: S.of(context).remove,
onPressed: () {
var pageToJump = (controller.page?.round() ?? 0) - 1;
pageToJump = pageToJump > 0 ? pageToJump : 0;
final output = _defineCurrentOutput();
sendViewModel.removeOutput(output);
controller.jumpToPage(pageToJump);
},
)
: TrailButton(
caption: S.of(context).clear,
onPressed: () {
final output = _defineCurrentOutput();
_formKey.currentState?.reset();
output.reset();
},
);
},
builder: (_) => sendViewModel.isBatchSending
? TrailButton(
caption: S.of(context).remove,
onPressed: () {
var pageToJump = (controller.page?.round() ?? 0) - 1;
pageToJump = pageToJump > 0 ? pageToJump : 0;
final output = _defineCurrentOutput();
sendViewModel.removeOutput(output);
controller.jumpToPage(pageToJump);
},
)
: TrailButton(
caption: S.of(context).clear,
onPressed: () {
final output = _defineCurrentOutput();
_formKey.currentState?.reset();
output.reset();
},
),
);
@override
@@ -183,7 +180,7 @@ class SendPage extends BasePage {
return Observer(builder: (_) {
List<Widget> sendCards = [];
List<KeyboardActionsItem> keyboardActions = [];
for (var output in sendViewModel.outputs) {
for (final output in sendViewModel.outputs) {
final isCurrent = sendViewModel.outputs.indexOf(output) == value;
sendCards.add(
SendCard(
@@ -267,7 +264,7 @@ class SendPage extends BasePage {
dotColor: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.4),
.withAlpha(100),
activeDotColor: Theme.of(context).colorScheme.primary,
),
),
@@ -418,12 +415,11 @@ class SendPage extends BasePage {
return;
}
final notValidItems = sendViewModel.outputs
.where(
(item) => item.address.isEmpty || item.cryptoAmount.isEmpty)
.toList();
final hasInvalidItems = sendViewModel.outputs.any((item) =>
item.address.isEmpty ||
(item.cryptoAmount.isEmpty && !item.sendAll));
if (notValidItems.isNotEmpty) {
if (hasInvalidItems) {
showErrorValidationAlert(context);
return;
}
@@ -500,9 +496,7 @@ class SendPage extends BasePage {
BuildContext? loadingBottomSheetContext;
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
return;
}
if (_effectsInstalled) return;
if (sendViewModel.isElectrumWallet) {
bitcoin!.updateFeeRates(sendViewModel.wallet);
@@ -529,16 +523,14 @@ class SendPage extends BasePage {
(_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
key: ValueKey('send_page_send_failure_dialog_key'),
buttonKey: ValueKey('send_page_send_failure_dialog_button_key'),
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
builder: (context) => AlertWithOneAction(
key: ValueKey('send_page_send_failure_dialog_key'),
buttonKey: ValueKey('send_page_send_failure_dialog_button_key'),
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
),
);
},
);
@@ -557,7 +549,7 @@ class SendPage extends BasePage {
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext context) {
builder: (context) {
loadingBottomSheetContext = context;
return LoadingBottomSheet(
titleText: S.of(context).generating_transaction,
@@ -577,34 +569,37 @@ class SendPage extends BasePage {
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) {
return Observer(
builder: (_) => ConfirmSendingBottomSheet(
key: ValueKey('send_page_confirm_sending_bottom_sheet_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
accessibleNavigationModeSlideActionButtonText:
S.of(bottomSheetContext).send,
footerType: FooterType.slideActionButton,
isSlideActionEnabled: sendViewModel.isReadyForSend,
walletType: sendViewModel.walletType,
titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
currency: sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue: sendViewModel.pendingTransaction!.amountFormatted,
fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted,
fee: isEVMCompatibleChain(sendViewModel.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
feeFiatAmount:
sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: sendViewModel.outputs,
onSlideActionComplete: () async {
Navigator.of(bottomSheetContext).pop(true);
sendViewModel.commitTransaction(context);
},
change: sendViewModel.pendingTransaction!.change,
isOpenCryptoPay: sendViewModel.ocpRequest != null,
),
);
builder: (_) => ConfirmSendingBottomSheet(
key: ValueKey('send_page_confirm_sending_bottom_sheet_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
accessibleNavigationModeSlideActionButtonText: S.of(bottomSheetContext).send,
footerType: FooterType.slideActionButton,
isSlideActionEnabled: sendViewModel.isReadyForSend,
walletType: sendViewModel.walletType,
titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
currency: sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue: sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
sendViewModel.pendingTransaction!.amountFormatted,
sendViewModel.selectedCryptoCurrency),
fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted,
fee: isEVMCompatibleChain(sendViewModel.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue: "${sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
sendViewModel.pendingTransaction!.feeFormattedValue,
sendViewModel.selectedCryptoCurrency)} ${sendViewModel.amountParsingProxy
.getCryptoSymbol(sendViewModel.wallet.currency)}",
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: sendViewModel.outputs,
onSlideActionComplete: () async {
Navigator.of(bottomSheetContext).pop(true);
sendViewModel.commitTransaction(context);
},
change: sendViewModel.pendingTransaction!.change,
isOpenCryptoPay: sendViewModel.ocpRequest != null,
amountParsingProxy: sendViewModel.amountParsingProxy,
),);
},
);
@@ -615,9 +610,7 @@ class SendPage extends BasePage {
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!context.mounted) {
return;
}
if (!context.mounted) return;
newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
@@ -788,37 +781,34 @@ class SendPage extends BasePage {
}
Output _defineCurrentOutput() {
if (controller.page == null) {
throw Exception('Controller page is null');
}
if (controller.page == null) throw Exception('Controller page is null');
final itemCount = controller.page!.round();
return sendViewModel.outputs[itemCount];
}
void showErrorValidationAlert(BuildContext context) async {
await showPopUp<void>(
void showErrorValidationAlert(BuildContext context) => showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: 'Please, check receiver forms',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: 'Please, check receiver forms',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
),
);
bool isRegularElectrumAddress(String address) {
final supportedTypes = [CryptoCurrency.btc, CryptoCurrency.ltc, CryptoCurrency.bch];
final excludedPatterns = [
RegExp(AddressValidator.silentPaymentAddressPatternMainnet),
RegExp(AddressValidator.silentPaymentAddressPatternTestnet),
RegExp(AddressValidator.mWebAddressPattern)
RegExp(AddressValidator.mWebAddressPattern),
RegExp(AddressValidator.bolt11InvoiceMatcher),
];
final trimmed = address.trim();
bool isValid = false;
for (var type in supportedTypes) {
for (final type in supportedTypes) {
final addressPattern = AddressValidator.getAddressFromStringPattern(type);
if (addressPattern != null) {
final regex = RegExp('^$addressPattern\$');
@@ -829,23 +819,16 @@ class SendPage extends BasePage {
}
}
for (var pattern in excludedPatterns) {
if (pattern.hasMatch(trimmed)) {
return false;
}
for (final pattern in excludedPatterns) {
if (pattern.hasMatch(trimmed)) return false;
}
return isValid;
}
String _sendButtonText(BuildContext context) {
if (!sendViewModel.isReadyForSend) {
return S.of(context).synchronizing;
}
if (sendViewModel.payjoinUri != null) {
return S.of(context).send_payjoin;
} else {
return S.of(context).send;
}
if (!sendViewModel.isReadyForSend) return S.of(context).synchronizing;
if (sendViewModel.payjoinUri != null) return S.of(context).send_payjoin;
return S.of(context).send;
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.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/src/screens/send/widgets/send_template_card.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
@@ -6,9 +6,9 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'choose_yat_address_alert.dart';
Future<String> extractAddressFromParsed(
BuildContext context,
BuildContext? context,
ParsedAddress parsedAddress) async {
if (!context.mounted) return parsedAddress.addresses.first;
if (context == null || !context.mounted) return parsedAddress.addresses.first;
var title = '';
var content = '';
@@ -83,6 +83,11 @@ Future<String> extractAddressFromParsed(
content = S.of(context).extracted_address_content('${parsedAddress.name} (BIP-353)');
address = parsedAddress.addresses.first;
break;
case ParseFrom.lnurlpay:
title = S.of(context).address_detected;
content = S.of(context).extracted_address_content('${parsedAddress.name} (Lightning)');
address = parsedAddress.addresses.first;
break;
case ParseFrom.yatRecord:
if (parsedAddress.name.isEmpty) {
title = S.of(context).yat_error;
+38 -24
View File
@@ -155,6 +155,11 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
Future<void> _handlePaymentFlow(String uri, PaymentRequest paymentRequest) async {
if (uri.contains('@') || paymentRequest.address.contains('@')) return;
if (OpenCryptoPayService.isOpenCryptoPayQR(uri)) {
sendViewModel.createOpenCryptoPayTransaction(uri);
return;
}
try {
final result = await paymentViewModel.processAddress(uri);
@@ -588,15 +593,14 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
focusNode: addressFocusNode,
controller: addressController,
onURIScanned: (uri) async {
if (OpenCryptoPayService.isOpenCryptoPayQR(uri.toString())) {
sendViewModel.createOpenCryptoPayTransaction(uri.toString());
} else {
// Process the payment through the new flow
await _handlePaymentFlow(
uri.toString(),
PaymentRequest.fromUri(uri),
);
}
output.resetParsedAddress();
await output.fetchParsedAddress(context);
// Process the payment through the new flow
await _handlePaymentFlow(
uri.toString(),
PaymentRequest.fromUri(uri),
);
},
options: [
AddressTextFieldOption.paste,
@@ -670,8 +674,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
sendAllButtonKey: ValueKey('send_page_send_all_button_key'),
currencyAmountTextFieldWidgetKey:
ValueKey('send_page_crypto_currency_amount_textfield_widget_key'),
selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
selectedCurrencyDecimals: sendViewModel.selectedCryptoCurrency.decimals,
selectedCurrency: output.useSatoshi ? "SATS" : sendViewModel.selectedCryptoCurrency.title,
selectedCurrencyDecimals: output.useSatoshi ? 0 : sendViewModel.selectedCryptoCurrency.decimals,
amountFocusNode: widget.cryptoAmountFocus,
amountController: cryptoAmountController,
isAmountEditable: true,
@@ -774,7 +778,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
),
),
),
if (sendViewModel.feesViewModel.hasFees)
if (sendViewModel.hasFees)
Observer(
builder: (_) => GestureDetector(
key: ValueKey('send_page_select_fee_priority_button_key'),
@@ -802,9 +806,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output.estimatedFee.toString() +
' ' +
sendViewModel.currency.toString(),
'${output.estimatedFee} ${sendViewModel.currencySymbol}',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
),
@@ -814,9 +816,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(
output.estimatedFeeFiatAmount +
' ' +
sendViewModel.fiat.title,
'${output.estimatedFeeFiatAmount} ${sendViewModel.fiat.title}',
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
@@ -880,7 +880,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
),
),
),
if (sendViewModel.currency == CryptoCurrency.ltc && sendViewModel.isMwebEnabled)
if (sendViewModel.isMwebAvailable)
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 14),
@@ -942,8 +942,14 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
output.sendAll = false;
}
if (amount != output.cryptoAmount) {
output.setCryptoAmount(amount);
if (S.current.all.contains(amount)) return;
final cAmount = sendViewModel.amountParsingProxy
.getDisplayCryptoAmount(output.cryptoAmount, sendViewModel.selectedCryptoCurrency);
if (amount != cAmount) {
final newAmount = sendViewModel.amountParsingProxy
.getCanonicalCryptoAmount(amount, sendViewModel.selectedCryptoCurrency);
output.setCryptoAmount(newAmount);
}
});
@@ -973,7 +979,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
output.setSendAll(await sendViewModel.sendingBalance);
}
output.setCryptoAmount(cryptoAmountController.text);
output.setCryptoAmount(sendViewModel.amountParsingProxy
.getCanonicalCryptoAmount(cryptoAmountController.text, sendViewModel.selectedCryptoCurrency));
});
reaction((_) => output.fiatAmount, (String amount) {
@@ -987,8 +994,11 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
output.sendAll = false;
}
if (amount != cryptoAmountController.text) {
cryptoAmountController.text = amount;
final cryptoAmount = sendViewModel.amountParsingProxy
.getCanonicalCryptoAmount(cryptoAmountController.text, sendViewModel.selectedCryptoCurrency);
if (amount != cryptoAmount) {
cryptoAmountController.text = sendViewModel.amountParsingProxy
.getDisplayCryptoAmount(amount, sendViewModel.selectedCryptoCurrency);
}
});
@@ -1004,6 +1014,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
if (output.address != address) {
output.resetParsedAddress();
output.address = address;
if (SendViewModelBase.isNonZeroAmountLightningInvoice(address)) {
sendViewModel.createTransaction();
}
}
});
@@ -1,178 +1,199 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/entities/sync_status_display_mode.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_theme_choice.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:image_picker/image_picker.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class DisplaySettingsPage extends BasePage {
class DisplaySettingsPage extends StatelessWidget {
DisplaySettingsPage(this._displaySettingsViewModel);
@override
String get title => S.current.display_settings;
final DisplaySettingsViewModel _displaySettingsViewModel;
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSwitcherCell(
title: S.of(context).apps,
value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShouldShowMarketPlaceInDashbaord(value);
},
),
SettingsSwitcherCell(
title: S.of(context).show_address_book_popup,
value: _displaySettingsViewModel.showAddressBookPopup,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowAddressBookPopup(value);
},
),
if (getIt.get<AppStore>().wallet?.type == WalletType.zcash)
SettingsSwitcherCell(
title: S.of(context).show_zcash_card,
value: _displaySettingsViewModel.showZcashCard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowZcashCard(value);
},
),
SettingsPickerCell<SyncStatusDisplayMode>(
title: S.current.sync_status_display_mode,
items: SyncStatusDisplayMode.values.toList(),
selectedItem: _displaySettingsViewModel.syncStatusDisplayMode,
onItemSelected: (SyncStatusDisplayMode mode) =>
_displaySettingsViewModel.setSyncStatusDisplayMode(mode),
displayItem: (SyncStatusDisplayMode mode) => mode.title,
isGridView: false,
),
//if (!isHaven) it does not work correctly
if (!_displaySettingsViewModel.disabledFiatApiMode)
SettingsPickerCell<FiatCurrency>(
title: S.of(context).settings_currency,
searchHintText: S.of(context).search_currency,
items: FiatCurrency.all,
selectedItem: _displaySettingsViewModel.fiatCurrency,
onItemSelected: (FiatCurrency currency) =>
_displaySettingsViewModel.setFiatCurrency(currency),
images: FiatCurrency.all
.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png"))
.toList(),
isGridView: true,
matchingCriteria: (FiatCurrency currency, String searchText) {
return currency.title.toLowerCase().contains(searchText) ||
currency.fullName.toLowerCase().contains(searchText);
},
),
SettingsPickerCell<String>(
title: S.of(context).settings_change_language,
searchHintText: S.of(context).search_language,
items: LanguageService.list.keys.toList(),
displayItem: (dynamic code) {
return LanguageService.list[code] ?? '';
},
selectedItem: _displaySettingsViewModel.languageCode,
onItemSelected: _displaySettingsViewModel.onLanguageSelected,
images: LanguageService.list.keys
.map((e) => Image.asset(
"assets/images/flags/${LanguageService.localeCountryCode[e]}.png"))
.toList(),
matchingCriteria: (String code, String searchText) {
return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false;
},
),
if (FeatureFlag.customBackgroundEnabled)
StandardListRow(
title: "Custom background",
isSelected: false,
onTap: (_) => _pickImage(context),
),
if (responsiveLayoutUtil.shouldRenderMobileUI && DeviceInfo.instance.isMobile) ...[
SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.appearance,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 22 / 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
SizedBox(height: 12),
Container(
margin: EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
SettingsChoicesCell(
ChoicesListItem<ThemeMode>(
title: "",
items: ThemeMode.values,
selectedItem: _displaySettingsViewModel.themeMode,
onItemSelected: (ThemeMode themeMode) =>
_displaySettingsViewModel.setThemeMode(themeMode),
displayItem: (ThemeMode themeMode) {
return themeMode.name[0].toUpperCase() +
themeMode.name.substring(1).toLowerCase();
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
top: false,
child: Column(
children: [
ModalTopBar(title: S.of(context).display_settings,leadingIcon: Icon(Icons.arrow_back_ios_new),onLeadingPressed: () => Navigator.of(context).pop()),
Expanded(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
physics: ClampingScrollPhysics(),
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSwitcherCell(
title: S.of(context).apps,
value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShouldShowMarketPlaceInDashbaord(value);
},
),
useGenericColor: false,
padding: EdgeInsets.all(12),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16),
SettingsSwitcherCell(
title: S.of(context).show_address_book_popup,
value: _displaySettingsViewModel.showAddressBookPopup,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowAddressBookPopup(value);
},
),
child: Column(
children: [
Semantics(
label: S.of(context).color_theme,
child: SettingsThemeChoicesCell(_displaySettingsViewModel),
if (_displaySettingsViewModel.showZcashCardSetting)
SettingsSwitcherCell(
title: S.of(context).show_zcash_card,
value: _displaySettingsViewModel.showZcashCard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowZcashCard(value);
},
),
SettingsPickerCell<SyncStatusDisplayMode>(
title: S.current.sync_status_display_mode,
items: SyncStatusDisplayMode.values.toList(),
selectedItem: _displaySettingsViewModel.syncStatusDisplayMode,
onItemSelected: (SyncStatusDisplayMode mode) =>
_displaySettingsViewModel.setSyncStatusDisplayMode(mode),
displayItem: (SyncStatusDisplayMode mode) => mode.title,
isGridView: false,
),
if (_displaySettingsViewModel.showDisplayAmountsInSatoshiSetting)
SettingsPickerCell<BitcoinAmountDisplayMode>(
title: S.of(context).bitcoin_amount_display,
items: BitcoinAmountDisplayMode.all,
selectedItem: _displaySettingsViewModel.displayAmountsInSatoshi,
onItemSelected: _displaySettingsViewModel.setDisplayAmountsInSatoshi,
displayItem: (mode) => mode.title,
isGridView: false,
),
//if (!isHaven) it does not work correctly
if (!_displaySettingsViewModel.disabledFiatApiMode)
SettingsPickerCell<FiatCurrency>(
title: S.of(context).settings_currency,
searchHintText: S.of(context).search_currency,
items: FiatCurrency.all,
selectedItem: _displaySettingsViewModel.fiatCurrency,
onItemSelected: (FiatCurrency currency) =>
_displaySettingsViewModel.setFiatCurrency(currency),
images: FiatCurrency.all
.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png"))
.toList(),
isGridView: true,
matchingCriteria: (FiatCurrency currency, String searchText) {
return currency.title.toLowerCase().contains(searchText) ||
currency.fullName.toLowerCase().contains(searchText);
},
),
SettingsPickerCell<String>(
title: S.of(context).settings_change_language,
searchHintText: S.of(context).search_language,
items: LanguageService.list.keys.toList(),
displayItem: (dynamic code) {
return LanguageService.list[code] ?? '';
},
selectedItem: _displaySettingsViewModel.languageCode,
onItemSelected: _displaySettingsViewModel.onLanguageSelected,
images: LanguageService.list.keys
.map((e) => Image.asset(
"assets/images/flags/${LanguageService.localeCountryCode[e]}.png"))
.toList(),
matchingCriteria: (String code, String searchText) {
return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false;
},
),
if (FeatureFlag.customBackgroundEnabled)
StandardListRow(
title: "Custom background",
isSelected: false,
onTap: (_) => _pickImage(context),
),
if (responsiveLayoutUtil.shouldRenderMobileUI && DeviceInfo.instance.isMobile) ...[
SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.appearance,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 22 / 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
],
),
),
],
],
),
);
}),
),
SizedBox(height: 12),
Container(
margin: EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
SettingsChoicesCell(
ChoicesListItem<ThemeMode>(
title: "",
items: ThemeMode.values,
selectedItem: _displaySettingsViewModel.themeMode,
onItemSelected: (ThemeMode themeMode) =>
_displaySettingsViewModel.setThemeMode(themeMode),
displayItem: (ThemeMode themeMode) {
return themeMode.name[0].toUpperCase() +
themeMode.name.substring(1).toLowerCase();
},
),
useGenericColor: false,
padding: EdgeInsets.all(12),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Semantics(
label: S.of(context).color_theme,
child: SettingsThemeChoicesCell(_displaySettingsViewModel),
),
],
),
),
],
),
),
],
],
),
);
}),
),
),
],
),
),
);
}
@@ -11,6 +11,7 @@ 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:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ManageNodesPage extends BasePage {
ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel})
@@ -60,6 +61,7 @@ class ManageNodesPage extends BasePage {
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
return Flexible(
child: SectionStandardList(
scrollController: ModalScrollController.of(context),
sectionCount: 1,
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) => itemsCount,
+7 -7
View File
@@ -23,13 +23,13 @@ class MwebSettingsPage extends BasePage {
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsSwitcherCell(
title: S.current.litecoin_mweb_display_card,
value: _mwebSettingsViewModel.mwebCardDisplay,
onValueChange: (_, bool value) {
_mwebSettingsViewModel.setMwebCardDisplay(value);
},
),
// SettingsSwitcherCell(
// title: S.current.litecoin_mweb_display,
// value: _mwebSettingsViewModel.mwebCardDisplay,
// onValueChange: (_, bool value) {
// _mwebSettingsViewModel.setMwebCardDisplay(value);
// },
// ),
SettingsSwitcherCell(
title: S.current.litecoin_mweb_enable,
value: _mwebSettingsViewModel.mwebEnabled,
@@ -9,17 +9,13 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/db/sqlite.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart';
import 'package:path/path.dart' as p;
class OtherSettingsPage extends BasePage {
OtherSettingsPage(this._otherSettingsViewModel) {
@@ -84,6 +80,16 @@ class OtherSettingsPage extends BasePage {
},
}),
),
if (_otherSettingsViewModel.walletType == WalletType.bitcoin) ...[
SettingsCellWithArrow(
title: S.of(context).export_lightning_logs,
handler: onExportLNLog,
),
SettingsCellWithArrow(
title: S.of(context).export_payjoin_logs,
handler: onExportPJLog,
),
],
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
@@ -176,4 +182,20 @@ class OtherSettingsPage extends BasePage {
),
),
);
Future<void> onExportLNLog(BuildContext context) async {
final file = await _otherSettingsViewModel.getLightningLog();
if (file != null) {
await ShareUtil.shareFile(filePath: file.path, fileName: "Lightning.log", context: context);
}
}
Future<void> onExportPJLog(BuildContext context) async {
final file = await _otherSettingsViewModel.getPayjoinLog();
if (file != null) {
await ShareUtil.shareFile(filePath: file.path, fileName: "Payjoin.log", context: context);
}
}
}
+22 -11
View File
@@ -49,6 +49,12 @@ class PrivacyPage extends BasePage {
_privacySettingsViewModel.setExchangeApiMode(mode),
),
),
if (_privacySettingsViewModel.canUseLightning)
SettingsSwitcherCell(
title: S.of(context).enable_lightning,
value: _privacySettingsViewModel.useLightning,
onValueChange: (_, value) => _privacySettingsViewModel.setUseLightning(value),
),
if (_privacySettingsViewModel.canUsePayjoin)
SettingsSwitcherCell(
title: S.of(context).use_payjoin,
@@ -95,13 +101,13 @@ class PrivacyPage extends BasePage {
_privacySettingsViewModel.setDisableAutomaticExchangeStatusUpdates(value);
},
),
SettingsSwitcherCell(
title: S.current.disable_bulletin,
value: _privacySettingsViewModel.disableBulletin,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setDisableBulletin(value);
},
),
// SettingsSwitcherCell(
// title: S.current.disable_bulletin,
// value: _privacySettingsViewModel.disableBulletin,
// onValueChange: (BuildContext _, bool value) {
// _privacySettingsViewModel.setDisableBulletin(value);
// },
// ),
if (_privacySettingsViewModel.canUseBlinkProtection)
SettingsSwitcherCell(
title: S.current.use_blink_protection,
@@ -189,10 +195,15 @@ class PrivacyPage extends BasePage {
title: S.current.domain_looks_up,
handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage),
),
SettingsCellWithArrow(
title: 'Trocador providers',
handler: (context) => Navigator.of(context).pushNamed(Routes.trocadorProvidersPage),
),
if(_privacySettingsViewModel.hasCoinControl)
SettingsCellWithArrow(
title: "Coin Control",
handler: (context) => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
),
// SettingsCellWithArrow(
// title: 'Trocador providers',
// handler: (context) => Navigator.of(context).pushNamed(Routes.trocadorProvidersPage),
// ),
],
);
}),
+164 -138
View File
@@ -1,16 +1,18 @@
import 'dart:io';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.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/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
@@ -26,7 +28,7 @@ class SecurityBackupPage extends BasePage {
final AuthService _authService;
@override
String get title => S.current.security_and_backup;
String get title => S.current.security;
final SecuritySettingsViewModel _securitySettingsViewModel;
@@ -36,144 +38,168 @@ class SecurityBackupPage extends BasePage {
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
Observer(builder: (_) {
return SettingsSwitcherCell(
key: ValueKey('security_backup_page_allow_biometrics_button_key'),
title: S.current.settings_allow_biometrical_authentication,
value: _securitySettingsViewModel.allowBiometricalAuthentication,
onValueChange: (BuildContext context, bool value) {
if (value) {
_authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
} else {
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);
}
});
}),
if (FeatureFlag.duressPinEnabled)
Observer(builder: (_) {
return SettingsSwitcherCell(
key: ValueKey('security_backup_page_duress_pin_button_key'),
title: 'Duress PIN',
value: _securitySettingsViewModel.enableDuressPin,
onValueChange: (BuildContext context, bool value) {
_authService.authenticateAction(context, route: Routes.securityBackupDuressPin,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (!value) {
_securitySettingsViewModel.setEnableDuressPin(value);
_securitySettingsViewModel.clearDuressPin();
return;
}
final res = await _showDuressPinDescription(context);
if (res) {
final confirmation = await _showDuressPinConfirmation(context);
if (confirmation) {
Navigator.of(context).pushNamed(
Routes.setupDuressPin,
arguments: (PinCodeState<PinCodeWidget> pinCtx, String _) async {
pinCtx.close();
_securitySettingsViewModel.setEnableDuressPin(true);
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column(
spacing: 16,
mainAxisSize: MainAxisSize.min,
children: [
ModalHeader(
iconPath: "assets/new-ui/settings_row_icons/security.svg",
message: S.of(context).privacy_and_security_desc,
title: S.of(context).security),
Observer(
builder: (_) => NewListSections(sections: {
"": [
if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
ListItemToggle(
keyValue: "security_backup_page_allow_biometrics_button_key",
label: S.current.settings_allow_biometrical_authentication,
value: _securitySettingsViewModel.allowBiometricalAuthentication,
onChanged: (bool value) {
if (value) {
_authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel.setAllowBiometricalAuthentication(
isAuthenticatedSuccessfully);
}
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(
isAuthenticatedSuccessfully);
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);
}
}
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
});
}),
Observer(builder: (_) {
return SettingsPickerCell<PinCodeRequiredDuration>(
key: ValueKey('security_backup_page_require_pin_after_button_key'),
title: S.current.require_pin_after,
items: PinCodeRequiredDuration.values,
selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
onItemSelected: (PinCodeRequiredDuration code) {
_securitySettingsViewModel.setPinCodeRequiredDuration(code);
},
);
}),
if (!_isHardwareWallet)
SettingsCellWithArrow(
key: ValueKey('security_backup_page_show_keys_button_key'),
title: S.current.show_keys,
handler: (_) => _authService.authenticateAction(
context,
route: Routes.showKeys,
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
}),
if (FeatureFlag.duressPinEnabled)
ListItemToggle(
keyValue: "security_backup_page_duress_pin_button_key",
label: "Duress PIN",
value: _securitySettingsViewModel.enableDuressPin,
onChanged: (bool value) {
_authService
.authenticateAction(context, route: Routes.securityBackupDuressPin,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (!value) {
_securitySettingsViewModel.setEnableDuressPin(value);
_securitySettingsViewModel.clearDuressPin();
return;
}
final res = await _showDuressPinDescription(context);
if (res) {
final confirmation = await _showDuressPinConfirmation(context);
if (confirmation) {
Navigator.of(context).pushNamed(
Routes.setupDuressPin,
arguments:
(PinCodeState<PinCodeWidget> pinCtx, String _) async {
pinCtx.close();
_securitySettingsViewModel.setEnableDuressPin(true);
},
);
}
}
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
}),
ListItemRegularRow(
keyValue: "security_backup_page_require_pin_after_button_key",
label: S.current.require_pin_after,
trailingText: _securitySettingsViewModel.pinCodeRequiredDuration.toString(),
onTap: () async {
final items = PinCodeRequiredDuration.values;
final selectedAtIndex =
items.indexOf(_securitySettingsViewModel.pinCodeRequiredDuration);
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: (PinCodeRequiredDuration item) {
_securitySettingsViewModel.setPinCodeRequiredDuration(item);
},
isSeparated: false,
),
);
}),
// if (!_isHardwareWallet)
// ListItemRegularRow(
// keyValue: "security_backup_page_show_keys_button_key",
// label: S.current.show_keys,
// onTap: () {
// _authService.authenticateAction(
// context,
// route: Routes.showKeys,
// conditionToDetermineIfToUse2FA: _securitySettingsViewModel
// .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
// );
// }),
// if (!SettingsStoreBase.walletPasswordDirectInput)
// ListItemRegularRow(
// keyValue: "security_backup_page_change_password_button_key",
// label: S.current.create_backup,
// onTap: () {
// _authService.authenticateAction(
// context,
// route: Routes.backup,
// conditionToDetermineIfToUse2FA: _securitySettingsViewModel
// .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
// );
// }),
ListItemRegularRow(
keyValue: "security_backup_page_change_pin_button_key",
label: S.current.settings_change_pin,
onTap: () {
_authService.authenticateAction(
context,
route: Routes.setupPin,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
}),
ListItemRegularRow(
keyValue: "security_backup_page_sign_and_verify",
label: S.current.sign_verify_title,
onTap: () {
Navigator.of(context).pushNamed(Routes.signPage);
}),
ListItemRegularRow(
keyValue: "security_backup_page_totp_2fa_button_key",
label: _securitySettingsViewModel.useTotp2FA
? S.current.modify_2fa
: S.current.setup_2fa,
onTap: () {
_authService.authenticateAction(
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup2faInfoPage,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
);
})
]
}),
),
if (!SettingsStoreBase.walletPasswordDirectInput)
SettingsCellWithArrow(
key: ValueKey('security_backup_page_create_backup_button_key'),
title: S.current.create_backup,
handler: (_) => _authService.authenticateAction(
context,
route: Routes.backup,
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
SettingsCellWithArrow(
key: ValueKey('security_backup_page_change_pin_button_key'),
title: S.current.settings_change_pin,
handler: (_) => _authService.authenticateAction(
context,
route: Routes.setupPin,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
},
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
SettingsCellWithArrow(
key: ValueKey('security_backup_page_sign_and_verify'),
title: S.current.sign_verify_title,
handler: (_) => Navigator.of(context).pushNamed(Routes.signPage)
//_securitySettingsViewModel.pinCodeRequiredDuration,
),
Observer(
builder: (context) {
return SettingsCellWithArrow(
key: ValueKey('security_backup_page_totp_2fa_button_key'),
title: _securitySettingsViewModel.useTotp2FA
? S.current.modify_2fa
: S.current.setup_2fa,
handler: (_) => _authService.authenticateAction(
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup2faInfoPage,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
);
},
),
],
],
),
),
);
}
@@ -9,6 +9,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:path_provider/path_provider.dart';
class SilentPaymentsLogPage extends BasePage {
@@ -33,6 +34,7 @@ class SilentPaymentsLogPage extends BasePage {
return Center(child: Text('No logs found'));
} else {
return SingleChildScrollView(
controller: ModalScrollController.of(context),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
@@ -1,55 +1,64 @@
import 'package:cake_wallet/generated/i18n.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/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class SilentPaymentsSettingsPage extends BasePage {
class SilentPaymentsSettingsPage extends StatelessWidget {
SilentPaymentsSettingsPage(this._silentPaymentsSettingsViewModel);
@override
String get title => S.current.silent_payments_settings;
final SilentPaymentsSettingsViewModel _silentPaymentsSettingsViewModel;
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsSwitcherCell(
title: S.current.silent_payments_display_card,
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
},
),
SettingsSwitcherCell(
title: S.current.silent_payments_always_scan,
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
},
),
SettingsCellWithArrow(
title: S.current.silent_payments_scanning,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
),
SettingsCellWithArrow(
title: S.current.silent_payments_logs,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.silentPaymentsLogs),
),
],
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
ModalTopBar(title: S.current.silent_payments_settings,leadingIcon: Icon(Icons.arrow_back_ios_new),onLeadingPressed: Navigator.of(context).pop,),
Expanded(
child: SingleChildScrollView(
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
if(!FeatureFlag.hasNewUi)
SettingsSwitcherCell(
title: S.current.silent_payments_display_card,
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
},
),
SettingsSwitcherCell(
title: S.current.silent_payments_always_scan,
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
},
),
SettingsCellWithArrow(
title: S.current.silent_payments_scanning,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
),
SettingsCellWithArrow(
title: S.current.silent_payments_logs,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.silentPaymentsLogs),
),
],
),
);
}),
),
),
);
}),
],
),
);
}
}
@@ -11,6 +11,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import '../../../routes.dart';
@@ -25,6 +26,7 @@ class Modify2FAPage extends BasePage {
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
controller: ModalScrollController.of(context),
child: _2FAControlsWidget(setup2FAViewModel: setup2FAViewModel),
);
}
@@ -1,6 +1,7 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/auth_state.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -92,7 +93,6 @@ class TotpAuthCodePageState extends State<TotpAuthCodePage> {
Widget build(BuildContext context) {
return Scaffold(
key: _key,
resizeToAvoidBottomInset: false,
body: TOTPEnterCode(
setup2FAViewModel: widget.setup2FAViewModel,
isForSetup: widget.totpArguments.isForSetup ?? false,
@@ -108,99 +108,120 @@ class TotpAuthCodePageState extends State<TotpAuthCodePage> {
}
}
class TOTPEnterCode extends BasePage {
TOTPEnterCode({
class TOTPEnterCode extends StatefulWidget {
const TOTPEnterCode({
required this.setup2FAViewModel,
required this.isForSetup,
required this.isClosable,
}) : totpController = TextEditingController() {
totpController.addListener(() {
setup2FAViewModel.enteredOTPCode = totpController.text;
});
}
});
@override
String get title => isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa;
Widget? leading(BuildContext context) {
return isClosable ? super.leading(context) : null;
}
final TextEditingController totpController;
final Setup2FAViewModel setup2FAViewModel;
final bool isForSetup;
final bool isClosable;
@override
Widget body(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
),
child: Column(
children: [
BaseTextFormField(
textAlign: TextAlign.left,
hintText: S.current.totp_code,
controller: totpController,
keyboardType: TextInputType.number,
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16,
),
),
SizedBox(height: 16),
Text(
S.current.please_fill_totp,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
height: 1.2,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
Spacer(),
Observer(
builder: (context) {
return PrimaryButton(
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8,
onPressed: () async {
final result =
await setup2FAViewModel.totp2FAAuth(totpController.text, isForSetup);
final bannedState = setup2FAViewModel.state is AuthenticationBanned;
State<TOTPEnterCode> createState() => _TOTPEnterCodeState();
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return PopUpCancellableAlertDialog(
contentText: _textDisplayedInPopupOnResult(result, bannedState, context),
actionButtonText: S.of(context).ok,
buttonAction: () {
result ? setup2FAViewModel.success() : null;
if (isForSetup && result) {
Navigator.pop(context);
// Navigator.of(context)
// .popAndPushNamed(Routes.modify2FAPage);
} else {
Navigator.of(context).pop(result);
}
},
);
},
);
if (isForSetup && result) {
if (context.mounted) {
Navigator.pushReplacementNamed(context, Routes.modify2FAPage);
}
}
},
text: S.of(context).continue_text,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
);
},
),
SizedBox(height: 24),
],
class _TOTPEnterCodeState extends State<TOTPEnterCode> {
final totpController = TextEditingController();
@override
void initState() {
super.initState();
totpController.addListener(() {
widget.setup2FAViewModel.enteredOTPCode = totpController.text;
});
}
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: Column(
children: [
ModalTopBar(
title: widget.isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
),
child: Column(
children: [
BaseTextFormField(
textAlign: TextAlign.left,
hintText: S.current.totp_code,
controller: totpController,
keyboardType: TextInputType.number,
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16,
),
),
SizedBox(height: 16),
Text(
S.current.please_fill_totp,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
height: 1.2,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
Spacer(),
Observer(
builder: (context) {
return PrimaryButton(
isDisabled: widget.setup2FAViewModel.enteredOTPCode.length != 8,
onPressed: () async {
final result =
await widget.setup2FAViewModel.totp2FAAuth(totpController.text, widget.isForSetup);
final bannedState = widget.setup2FAViewModel.state is AuthenticationBanned;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return PopUpCancellableAlertDialog(
contentText: _textDisplayedInPopupOnResult(result, bannedState, context),
actionButtonText: S.of(context).ok,
buttonAction: () {
result ? widget.setup2FAViewModel.success() : null;
if (widget.isForSetup && result) {
Navigator.pop(context);
// Navigator.of(context)
// .popAndPushNamed(Routes.modify2FAPage);
} else {
Navigator.of(context).pop(result);
}
},
);
},
);
if (widget.isForSetup && result) {
if (context.mounted) {
Navigator.pushReplacementNamed(context, Routes.modify2FAPage);
}
}
},
text: S.of(context).continue_text,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
);
},
),
SizedBox(height: 24),
],
),
),
),
],
),
),
);
}
@@ -208,10 +229,10 @@ class TOTPEnterCode extends BasePage {
String _textDisplayedInPopupOnResult(bool result, bool bannedState, BuildContext context) {
switch (result) {
case true:
return isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
return widget.isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
case false:
if (bannedState) {
final state = setup2FAViewModel.state as AuthenticationBanned;
final state = widget.setup2FAViewModel.state as AuthenticationBanned;
return S.of(context).failed_authentication(state.error);
} else {
return S.current.totp_2fa_failure;
@@ -41,7 +41,7 @@ class Setup2FAQRPage extends BasePage {
),
SizedBox(height: 10),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.4),
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
+18 -26
View File
@@ -34,35 +34,27 @@ class SupportPage extends BasePage {
@override
Widget body(BuildContext context) => Center(
child: Container(
padding: const EdgeInsets.only(left: 24, right: 24),
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
child: Column(
spacing: 16,
children: [
Padding(
padding: const EdgeInsets.only(top: 20),
child: OptionTile(
image: Image.asset(_imageSupportChat, width: 55, height: 55),
title: S.of(context).support_title_live_chat,
description: S.of(context).support_description_live_chat,
onPressed: () => _onPressedSupportChat(context),
),
OptionTile(
image: Image.asset(_imageSupportChat, width: 55, height: 55),
title: S.of(context).support_title_live_chat,
description: S.of(context).support_description_live_chat,
onPressed: () => _onPressedSupportChat(context),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: OptionTile(
image: Image.asset(_imageSupportDocs, width: 55, height: 55),
title: S.of(context).support_title_guides,
description: S.of(context).support_description_guides,
onPressed: () => _launchUrl(supportViewModel.docsUrl),
),
OptionTile(
image: Image.asset(_imageSupportDocs, width: 55, height: 55),
title: S.of(context).support_title_guides,
description: S.of(context).support_description_guides,
onPressed: () => _launchUrl(supportViewModel.docsUrl),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: OptionTile(
image: Image.asset(_imageSupportLinks, width: 55, height: 55),
title: S.of(context).support_title_other_links,
description: S.of(context).support_description_other_links,
onPressed: () => Navigator.pushNamed(context, Routes.supportOtherLinks),
),
OptionTile(
image: Image.asset(_imageSupportLinks, width: 55, height: 55),
title: S.of(context).support_title_other_links,
description: S.of(context).support_description_other_links,
onPressed: () => Navigator.pushNamed(context, Routes.supportOtherLinks),
),
],
),
@@ -73,7 +65,7 @@ class SupportPage extends BasePage {
if (DeviceInfo.instance.isDesktop) {
_launchUrl(supportViewModel.fetchUrl());
} else {
Navigator.pushNamed(context, Routes.supportLiveChat);
Navigator.of(context, rootNavigator: true).pushNamed(Routes.supportLiveChat);
}
}
@@ -1,39 +1,52 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/screens/support_chat/widgets/chatwoot_widget.dart';
import 'package:cake_wallet/view_model/support_view_model.dart';
import 'package:flutter/material.dart';
class SupportChatPage extends BasePage {
class SupportChatPage extends StatelessWidget {
SupportChatPage(this.supportViewModel, {required this.secureStorage});
final SupportViewModel supportViewModel;
final SecureStorage secureStorage;
@override
String get title => S.current.settings_support;
@override
AppBarStyle get appBarStyle => AppBarStyle.regular;
@override
Widget body(BuildContext context) => FutureBuilder<String>(
future: getCookie(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData)
return ChatwootWidget(
secureStorage,
supportUrl: supportViewModel.fetchUrl(authToken: snapshot.data!),
appVersion: supportViewModel.appVersion,
fiatApiMode: supportViewModel.fiatApiMode,
walletType: supportViewModel.walletType,
walletSyncState: supportViewModel.walletSyncState,
builtInTorState: supportViewModel.builtInTorState,
);
return Container();
},
);
Widget build(BuildContext context) => Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
children: [
ModalTopBar(
title: S.current.settings_support,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop),
FutureBuilder<String>(
future: getCookie(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData)
return Expanded(
child: ChatwootWidget(
secureStorage,
supportUrl: supportViewModel.fetchUrl(authToken: snapshot.data!),
appVersion: supportViewModel.appVersion,
fiatApiMode: supportViewModel.fiatApiMode,
walletType: supportViewModel.walletType,
walletSyncState: supportViewModel.walletSyncState,
builtInTorState: supportViewModel.builtInTorState,
),
);
return Container();
},
),
],
),
),
),
);
Future<String> getCookie() async =>
await secureStorage.read(key: COOKIE_KEY) ?? "";
@@ -7,6 +7,7 @@ import 'package:cake_wallet/view_model/support_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class SupportOtherLinksPage extends BasePage {
SupportOtherLinksPage(this.supportViewModel);
@@ -29,6 +30,8 @@ class SupportOtherLinksPage extends BasePage {
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: SectionStandardList(
scrollController: ModalScrollController.of(context),
physics: ClampingScrollPhysics(),
sectionCount: 1,
itemCounter: (int _) => supportViewModel.items.length,
itemBuilder: (_, index) {
@@ -216,6 +216,7 @@ class RBFDetailsPage extends BasePage {
} catch (_) {}
},
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
);
},
);
+1 -2
View File
@@ -57,7 +57,7 @@ class AnimatedURPage extends BasePage {
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(32.0),
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: URQR(
urqr: urQr,
walletType: animatedURmodel.wallet.type,
@@ -66,7 +66,6 @@ class AnimatedURPage extends BasePage {
),
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header]
.contains(urQrType)) ...{
Spacer(),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
@@ -2,6 +2,6 @@ import 'dart:math';
extension StringParsing on String {
String safeSubString(int start, int end) {
return this.substring(0, min(this.toString().length, 12));
return this.substring(start, min(this.toString().length, end));
}
}
@@ -14,6 +14,7 @@ import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:qr_flutter/qr_flutter.dart';
class WalletKeysPage extends BasePage {
@@ -214,6 +215,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
children: [
Expanded(
child: ListView.separated(
controller: ModalScrollController.of(context),
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
@@ -237,7 +239,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(18),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Row(
@@ -294,7 +296,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(18),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Row(
+241 -196
View File
@@ -17,9 +17,12 @@ import 'package:cake_wallet/src/screens/wallet_list/edit_wallet_button_widget.da
import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/store/settings_store.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/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -28,6 +31,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -40,6 +44,9 @@ class WalletListPage extends BasePage {
this.onWalletLoaded,
});
@override
bool get gradientBackground => true;
final WalletListViewModel walletListViewModel;
final AuthService authService;
final Future<void> Function(BuildContext)? onWalletLoaded;
@@ -99,7 +106,7 @@ class WalletListPage extends BasePage {
),
child: Image.asset(
'assets/images/filter_icon.png',
color: Theme.of(context).colorScheme.onSurfaceVariant,
color: Theme.of(context).colorScheme.primary,
),
),
),
@@ -135,204 +142,207 @@ class WalletListBodyState extends State<WalletListBody> {
@override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
padding: EdgeInsets.only(top: 16),
child: Stack(
alignment: Alignment.bottomCenter,
fit: StackFit.expand,
children: <Widget>[
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.walletListViewModel.multiWalletGroups.isNotEmpty) ...{
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.shared_seed_wallet_groups,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
SizedBox(height: 16),
Container(
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Observer(
builder: (_) => FilteredList(
shrinkWrap: true,
list: widget.walletListViewModel.multiWalletGroups,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (context, index) {
final group = widget.walletListViewModel.multiWalletGroups[index];
final groupName =
group.groupName ?? '${S.current.wallet_group} ${index + 1}';
widget.walletListViewModel.updateTileState(
index,
widget.walletListViewModel.expansionTileStateTrack[index] ?? false,
);
return GroupedWalletExpansionTile(
onExpansionChanged: (value) {
widget.walletListViewModel.updateTileState(index, value);
setState(() {});
},
shouldShowCurrentWalletPointer: true,
borderRadius: BorderRadius.all(Radius.circular(16)),
title: groupName,
tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'),
leadingWidget: Icon(
Icons.account_balance_wallet_outlined,
size: 28,
),
trailingWidget: EditWalletButtonWidget(
width: 88,
isGroup: true,
isExpanded:
widget.walletListViewModel.expansionTileStateTrack[index]!,
onTap: () {
final wallet = widget.walletListViewModel
.convertWalletInfoToWalletListItem(group.wallets.first);
Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: wallet,
isWalletGroup: true,
groupName: groupName,
walletGroupKey: group.groupKey,
),
);
},
),
childWallets: group.wallets.map((walletInfo) {
return widget.walletListViewModel
.convertWalletInfoToWalletListItem(walletInfo);
}).toList(),
isSelected: false,
onChildItemTapped: (wallet) =>
wallet.isCurrent ? null : _loadWallet(wallet),
childTrailingWidget: (item) {
return item.isCurrent
? SizedBox.shrink()
: EditWalletButtonWidget(
width: 64,
onTap: () => Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: item,
),
),
);
},
);
},
),
return GradientBackground(
scaffold: Container(
height: double.infinity,
padding: EdgeInsets.only(top: 16),
child: Stack(
alignment: Alignment.bottomCenter,
fit: StackFit.expand,
children: <Widget>[
SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.walletListViewModel.multiWalletGroups.isNotEmpty) ...{
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.shared_seed_wallets,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
),
SizedBox(height: 24),
},
if (widget.walletListViewModel.singleWalletsList.isNotEmpty) ...{
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.single_seed_wallets_group,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
SizedBox(height: 16),
Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Container(
child: Observer(
builder: (_) => FilteredList(
shrinkWrap: true,
list: widget.walletListViewModel.singleWalletsList,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (context, index) {
final wallet = widget.walletListViewModel.singleWalletsList[index];
final currentColor = wallet.isCurrent
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surface;
return GroupedWalletExpansionTile(
tileKey: ValueKey('single_wallets_expansion_tile_widget_$index'),
isCurrentlySelectedWallet: wallet.isCurrent,
leadingWidget: SizedBox(
width: wallet.isCurrent ? 56 : 40,
child: Row(
children: [
wallet.isCurrent
? Container(
height: 35,
width: 6,
margin: EdgeInsets.only(right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16),
bottomRight: Radius.circular(16),
),
color: currentColor,
),
)
: SizedBox(width: 6),
Image.asset(
getCryptoCurrencyForWalletListItem(
wallet.type,
).iconPath!,
width: 32,
height: 32,
),
],
SizedBox(height: 16),
Container(
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Observer(
builder: (_) => FilteredList(
shrinkWrap: true,
list: widget.walletListViewModel.multiWalletGroups,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (context, index) {
final group = widget.walletListViewModel.multiWalletGroups[index];
final groupName =
group.groupName ?? '${S.current.wallet_group} ${index + 1}';
widget.walletListViewModel.updateTileState(
index,
widget.walletListViewModel.expansionTileStateTrack[index] ?? false,
);
return GroupedWalletExpansionTile(
onExpansionChanged: (value) {
widget.walletListViewModel.updateTileState(index, value);
setState(() {});
},
shouldShowCurrentWalletPointer: true,
borderRadius: BorderRadius.all(Radius.circular(18)),
title: groupName,
tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'),
leadingWidget: CakeImageWidget(
imageUrl: "assets/new-ui/navbar/wallets.svg",
width: 28,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
title: wallet.name,
isSelected: false,
borderRadius: BorderRadius.all(Radius.circular(16)),
margin: EdgeInsets.only(left: 20, right: 20, bottom: 12),
onTitleTapped: () => wallet.isCurrent ? null : _loadWallet(wallet),
trailingWidget: wallet.isCurrent
? null
: EditWalletButtonWidget(
width: 64,
onTap: () {
Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: wallet,
trailingWidget: EditWalletButtonWidget(
width: 88,
isGroup: true,
isExpanded:
widget.walletListViewModel.expansionTileStateTrack[index]!,
onTap: () {
final wallet = widget.walletListViewModel
.convertWalletInfoToWalletListItem(group.wallets.first);
Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: wallet,
isWalletGroup: true,
groupName: groupName,
walletGroupKey: group.groupKey,
),
);
},
),
childWallets: group.wallets.map((walletInfo) {
return widget.walletListViewModel
.convertWalletInfoToWalletListItem(walletInfo);
}).toList(),
isSelected: false,
onChildItemTapped: (wallet) =>
wallet.isCurrent ? null : _loadWallet(wallet),
childTrailingWidget: (item) {
return item.isCurrent
? SizedBox.shrink()
: EditWalletButtonWidget(
width: 64,
onTap: () => Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: item,
),
),
);
},
),
);
},
},
);
},
),
),
),
),
),
SizedBox(height: 150),
},
],
SizedBox(height: 24),
},
if (widget.walletListViewModel.singleWalletsList.isNotEmpty) ...{
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.single_seed_wallets_group,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
SizedBox(height: 16),
Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Container(
child: Observer(
builder: (_) => FilteredList(
shrinkWrap: true,
list: widget.walletListViewModel.singleWalletsList,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (context, index) {
final wallet = widget.walletListViewModel.singleWalletsList[index];
final currentColor = wallet.isCurrent
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surface;
return GroupedWalletExpansionTile(
tileKey: ValueKey('single_wallets_expansion_tile_widget_$index'),
isCurrentlySelectedWallet: wallet.isCurrent,
leadingWidget: SizedBox(
width: wallet.isCurrent ? 56 : 40,
child: Row(
children: [
wallet.isCurrent
? Container(
height: 35,
width: 6,
margin: EdgeInsets.only(right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(18),
bottomRight: Radius.circular(18),
),
color: currentColor,
),
)
: SizedBox(width: 6),
Image.asset(
getCryptoCurrencyIconForWalletListItem(
wallet.type,
),
width: 32,
height: 32,
),
],
),
),
title: wallet.name,
isSelected: false,
borderRadius: BorderRadius.all(Radius.circular(18)),
margin: EdgeInsets.only(left: 20, right: 20, bottom: 12),
onTitleTapped: () => wallet.isCurrent ? null : _loadWallet(wallet),
trailingWidget: wallet.isCurrent
? null
: EditWalletButtonWidget(
width: 64,
onTap: () {
Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: wallet,
),
);
},
),
);
},
),
),
),
),
},
SizedBox(height: 250),
],
),
),
),
Positioned(
child: Stack(
Stack(
alignment: Alignment.bottomCenter,
children: [
IgnorePointer(
!FeatureFlag.hasNewUi
? IgnorePointer(
child: Container(
alignment: Alignment.bottomCenter,
alignment: Alignment.bottomCenter,
height: 185,
decoration: BoxDecoration(
gradient: LinearGradient(
@@ -347,9 +357,29 @@ class WalletListBodyState extends State<WalletListBody> {
),
),
),
)
: IgnorePointer(
child: Container(
height: 275,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Theme.of(context).colorScheme.surfaceDim.withAlpha(10),
Theme.of(context).colorScheme.surfaceDim.withAlpha(150),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255),
Theme.of(context).colorScheme.surfaceDim.withAlpha(255)
],
),
),
),
),
Container(
height: 120,
height: 240,
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(bottom: 24),
padding: EdgeInsets.only(left: 16, right: 16),
@@ -434,19 +464,24 @@ class WalletListBodyState extends State<WalletListBody> {
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
if(FeatureFlag.hasNewUi)
SizedBox(height:52.0)
],
),
),
],
),
),
],
],
),
),
);
}
Future<void> _loadWallet(WalletListItem wallet) async {
if (_loadingWallet) return;
if (_loadingWallet) {
printV("_loadWallet abandoned because _loadingWallet");
return;
}
_loadingWallet = true;
@@ -469,6 +504,7 @@ class WalletListBodyState extends State<WalletListBody> {
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
printV("!isAuthenticatedSuccessfully");
_loadingWallet = false;
return;
}
@@ -510,16 +546,16 @@ class WalletListBodyState extends State<WalletListBody> {
await widget.walletListViewModel.loadWallet(wallet);
// only pop the wallets route in mobile as it will go back to dashboard page
// in desktop platforms the navigation tree is different
unawaited(hideProgressText());
if (responsiveLayoutUtil.shouldRenderMobileUI) {
// await Future.delayed(Duration(seconds: 1));
// if (!this.mounted) return;
if (!this.mounted) return;
if (!context.mounted) return;
if (requireHardwareWalletConnection) {
Navigator.of(context).pop();
}
await widget.onWalletLoaded.call(context);
}
unawaited(hideProgressText());
} catch (e) {
await ExceptionHandler.resetLastPopupDate();
final err = e.toString();
@@ -534,16 +570,25 @@ class WalletListBodyState extends State<WalletListBody> {
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
);
_loadingWallet = false;
}
void changeProcessText(String text) {
_progressBar = createBar<void>(text, context, duration: null)..show(context);
try {
if (_progressBar != null) {
_progressBar!.dismiss();
}
_progressBar = createBar<void>(text, context, duration: null)
..show(context);
}catch(e){}
}
Future<void> hideProgressText() async {
await Future.delayed(Duration(milliseconds: 50), () {
_progressBar?.dismiss();
_progressBar = null;
try {
_progressBar?.dismiss();
_progressBar = null;
} catch (e) {}
});
}
}
@@ -1,5 +1,7 @@
import 'dart:io';
import 'package:cake_wallet/src/widgets/cake_image_widget.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:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@@ -86,150 +88,154 @@ class CreatePinWelcomePage extends BasePage {
return PopScope(
canPop: false,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Container(
alignment: Alignment.center,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: imageMaxWidth),
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: <Widget>[
AspectRatio(
aspectRatio: imageAspectRatio,
child: FittedBox(
child: CakeImageWidget(
imageUrl: welcomeImage,
child: SafeArea(
top: false,
bottom: Platform.isAndroid,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Container(
alignment: Alignment.center,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: imageMaxWidth),
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: <Widget>[
AspectRatio(
aspectRatio: imageAspectRatio,
child: FittedBox(
child: CakeImageWidget(
imageUrl: welcomeImage,
fit: BoxFit.contain,
),
fit: BoxFit.contain,
),
fit: BoxFit.contain,
),
),
Positioned(
top: (imageMaxWidth / imageAspectRatio) * 0.57,
child: Column(
children: <Widget>[
Text(
S.current.welcome,
style: Theme.of(context).textTheme.displayLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 60,
letterSpacing: 2.5,
color: Theme.of(context).colorScheme.primary,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
S.current.to.toLowerCase(),
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
SizedBox(width: 8),
if (!isMoneroOnly) ...[
SizedBox(width: 8),
CakeImageWidget(
height: 40,
imageUrl: cakeLogoThemed,
fit: BoxFit.contain,
),
SizedBox(width: 8),
],
Text(
appTitle(context),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
],
),
SizedBox(height: 48),
if (isMoneroOnly)
Positioned(
top: (imageMaxWidth / imageAspectRatio) * 0.57,
child: Column(
children: <Widget>[
Text(
appDescription(context),
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(fontWeight: FontWeight.w500),
S.current.welcome,
style: Theme.of(context).textTheme.displayLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 60,
letterSpacing: 2.5,
color: Theme.of(context).colorScheme.primary,
),
textAlign: TextAlign.center,
),
if (!isMoneroOnly)
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AnimatedTypingText(
words: [S.current.payments, S.current.privacy, S.current.security],
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
cursorColor: Theme.of(context).colorScheme.primary,
cursorHeight: 28,
cursorWidth: 4,
),
SizedBox(width: 4),
Text(
S.current.made_easy,
S.current.to.toLowerCase(),
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
SizedBox(width: 8),
if (!isMoneroOnly) ...[
SizedBox(width: 8),
CakeImageWidget(
height: 40,
imageUrl: cakeLogoThemed,
fit: BoxFit.contain,
),
SizedBox(width: 8),
],
Text(
appTitle(context),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
],
),
],
),
),
],
),
),
),
bottomSection: Column(
children: [
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
SizedBox(height: 48),
if (isMoneroOnly)
Text(
appDescription(context),
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
if (!isMoneroOnly)
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AnimatedTypingText(
words: [S.current.payments, S.current.privacy, S.current.security],
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
cursorColor: Theme.of(context).colorScheme.primary,
cursorHeight: 28,
cursorWidth: 4,
),
SizedBox(width: 4),
Text(
S.current.made_easy,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
],
),
children: [
TextSpan(
text: 'By continuing, you agree to our ',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
TextSpan(
text: 'Terms of Service',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.underline,
fontWeight: FontWeight.w700,
),
recognizer: TapGestureRecognizer()
..onTap = () => Navigator.pushNamed(context, Routes.readDisclaimer),
),
],
),
),
Padding(
padding: EdgeInsets.only(top: 24),
child: PrimaryButton(
key: ValueKey('create_pin_welcome_page_create_a_pin_button_key'),
onPressed: () => Navigator.pushNamed(context, Routes.welcomeWallet),
text: isWalletPasswordDirectInput ? S.current.set_up_a_wallet : S.current.set_a_pin,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
bottomSection: Column(
children: [
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
children: [
TextSpan(
text: 'By continuing, you agree to our ',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
TextSpan(
text: 'Terms of Service',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.underline,
fontWeight: FontWeight.w700,
),
recognizer: TapGestureRecognizer()
..onTap = () => Navigator.pushNamed(context, Routes.readDisclaimer),
),
],
),
),
),
SizedBox(height: 16),
],
Padding(
padding: EdgeInsets.only(top: 24),
child: PrimaryButton(
key: ValueKey('create_pin_welcome_page_create_a_pin_button_key'),
onPressed: () => Navigator.pushNamed(context, Routes.welcomeWallet),
text: isWalletPasswordDirectInput ? S.current.set_up_a_wallet : S.current.set_a_pin,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),
SizedBox(height: 16),
],
),
),
),
);
+1 -1
View File
@@ -1,5 +1,5 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.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/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@@ -1,7 +1,7 @@
import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
import 'package:cake_wallet/src/screens/yat/widgets/yat_page_indicator.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:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -1,7 +1,7 @@
import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
import 'package:cake_wallet/src/screens/yat/widgets/yat_page_indicator.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:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -1,7 +1,7 @@
import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
import 'package:cake_wallet/src/screens/yat/widgets/yat_page_indicator.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:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
+4 -1
View File
@@ -29,6 +29,7 @@ class AddressTextField<T extends Currency> extends StatelessWidget {
this.onPushPasteButton,
this.onPushAddressBookButton,
this.onPushAddressPickerButton,
this.onEditingComplete,
this.onSelectedContact,
this.selectedCurrency,
this.addressKey,
@@ -49,6 +50,7 @@ class AddressTextField<T extends Currency> extends StatelessWidget {
final Function(Uri)? onURIScanned;
final List<AddressTextFieldOption> options;
final FormFieldValidator<String>? validator;
final VoidCallback? onEditingComplete;
final Color? buttonColor;
final Color? fillColor;
@@ -77,6 +79,7 @@ class AddressTextField<T extends Currency> extends StatelessWidget {
borderWidth: borderWidth,
hasUnderlineBorder: hasUnderlineBorder,
key: addressKey,
onEditingComplete: onEditingComplete,
enableIMEPersonalizedLearning: false,
keyboardType: TextInputType.visiblePassword,
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
@@ -242,7 +245,7 @@ class AddressTextField<T extends Currency> extends StatelessWidget {
Future<void> _presetAddressBookPicker(BuildContext context) async {
final contact = await Navigator.of(context)
.pushNamed(Routes.pickerAddressBook, arguments: selectedCurrency);
.pushNamed(Routes.pickerAddressBook, arguments: [selectedCurrency, false]);
if (contact is ContactBase) {
controller?.text = contact.address;
+14 -5
View File
@@ -47,6 +47,7 @@ class BaseTextFormField extends StatelessWidget {
this.suffixIconConstraints,
super.key,
this.suffixText,
this.borderRadius = const BorderRadius.all(Radius.circular(18)), this.onEditingComplete,
});
final TextEditingController? controller;
@@ -82,6 +83,7 @@ class BaseTextFormField extends StatelessWidget {
final bool? autocorrect;
final bool? enableSuggestions;
final void Function(String)? onChanged;
final VoidCallback? onEditingComplete;
final EdgeInsetsGeometry? contentPadding;
final bool? alignLabelWithHint;
final FloatingLabelBehavior? floatingLabelBehavior;
@@ -92,6 +94,7 @@ class BaseTextFormField extends StatelessWidget {
final void Function(String)? onFieldSubmitted;
final bool hasUnderlineBorder;
final double borderWidth;
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
@@ -110,6 +113,7 @@ class BaseTextFormField extends StatelessWidget {
controller: controller,
keyboardType: keyboardType,
textInputAction: textInputAction,
onEditingComplete: onEditingComplete,
textAlign: textAlign,
autovalidateMode: autovalidateMode,
obscureText: obscureText,
@@ -147,44 +151,49 @@ class BaseTextFormField extends StatelessWidget {
labelStyle: !hasUnderlineBorder ? placeholderTextStyle : null,
border: hasUnderlineBorder
? UnderlineInputBorder(
borderRadius: borderRadius,
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
style: borderWidth == 0.0 ? BorderStyle.none : BorderStyle.solid,
width: borderWidth,
),
)
: null,
: OutlineInputBorder(borderRadius: borderRadius, borderSide: BorderSide.none),
focusedBorder: hasUnderlineBorder
? UnderlineInputBorder(
borderRadius: borderRadius,
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
style: borderWidth == 0.0 ? BorderStyle.none : BorderStyle.solid,
width: borderWidth,
),
)
: null,
: OutlineInputBorder(borderRadius: borderRadius, borderSide: BorderSide.none),
disabledBorder: hasUnderlineBorder
? UnderlineInputBorder(
borderRadius: borderRadius,
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
width: borderWidth,
),
)
: null,
: OutlineInputBorder(borderRadius: borderRadius, borderSide: BorderSide.none),
enabledBorder: hasUnderlineBorder
? UnderlineInputBorder(
borderRadius: borderRadius,
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
style: borderWidth == 0.0 ? BorderStyle.none : BorderStyle.solid,
width: borderWidth,
),
)
: null,
: OutlineInputBorder(borderRadius: borderRadius, borderSide: BorderSide.none),
errorBorder: hasUnderlineBorder
? UnderlineInputBorder(
borderRadius: borderRadius,
borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
)
: null,
: OutlineInputBorder(borderRadius: borderRadius, borderSide: BorderSide.none),
),
validator: validator,
);
+84 -84
View File
@@ -25,6 +25,7 @@ class BlockchainHeightWidget extends StatefulWidget {
this.bitcoinMempoolAPIEnabled,
required this.walletType,
this.blockHeightTextFieldKey,
this.heightController,
}) : super(key: key);
final Function(int)? onHeightChange;
@@ -33,6 +34,7 @@ class BlockchainHeightWidget extends StatefulWidget {
final bool hasDatePicker;
final bool isSilentPaymentsScan;
final bool isMwebScan;
final TextEditingController? heightController;
final bool doSingleScan;
final Future<bool>? bitcoinMempoolAPIEnabled;
final Function()? toggleSingleScan;
@@ -44,13 +46,15 @@ class BlockchainHeightWidget extends StatefulWidget {
class BlockchainHeightState extends State<BlockchainHeightWidget> {
final dateController = TextEditingController();
final restoreHeightController = TextEditingController();
late final TextEditingController restoreHeightController;
int get height => _height;
int _height = 0;
@override
void initState() {
restoreHeightController = widget.heightController ?? TextEditingController();
restoreHeightController.addListener(() {
if (restoreHeightController.text.isNotEmpty) {
widget.onHeightOrDateEntered?.call(true);
@@ -71,100 +75,96 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).unfocus(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: BaseTextFormField(
key: widget.blockHeightTextFieldKey,
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
),
),
)
],
),
if (widget.hasDatePicker) ...[
Padding(
padding: EdgeInsets.only(top: 15, bottom: 15),
child: Text(
S.of(context).widgets_or,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: BaseTextFormField(
key: widget.blockHeightTextFieldKey,
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: false,
child: Container(
child: InkWell(
onTap: () => _selectDate(context),
child: IgnorePointer(
child: BaseTextFormField(
controller: dateController,
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date
: S.of(context).widgets_restore_from_date,
),
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
),
),
)
))
],
),
if (widget.hasDatePicker) ...[
if (widget.isSilentPaymentsScan)
Padding(
padding: EdgeInsets.only(top: 15, bottom: 15),
child: Text(
S.of(context).widgets_or,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
Row(
children: <Widget>[
Flexible(
child: Container(
child: InkWell(
onTap: () => _selectDate(context),
child: IgnorePointer(
child: BaseTextFormField(
controller: dateController,
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date
: S.of(context).widgets_restore_from_date,
),
),
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).scan_one_block,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
))
],
Padding(
padding: const EdgeInsets.only(right: 8),
child: StandardSwitch(
value: widget.doSingleScan,
onTapped: () => widget.toggleSingleScan?.call(),
),
)
],
),
),
if (widget.isSilentPaymentsScan)
Padding(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).scan_one_block,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: StandardSwitch(
value: widget.doSingleScan,
onTapped: () => widget.toggleSingleScan?.call(),
),
)
],
),
),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date_or_blockheight
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
)
]
],
),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date_or_blockheight
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
)
]
],
);
}
@@ -1,3 +1,5 @@
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/rounded_icon_button.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
@@ -35,6 +37,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
required this.feeFiatAmount,
required this.outputs,
required this.walletType,
this.amountParsingProxy,
this.change,
this.explanation,
this.isOpenCryptoPay = false,
@@ -71,10 +74,14 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
final CakePayBuyCardViewModel? cakePayBuyCardViewModel;
final String? quantity;
final String? explanation;
final AmountParsingProxy? amountParsingProxy;
final bool showScrollbar;
final ScrollController scrollController = ScrollController();
bool get showAddress => !outputs
.any((e) => RegExp(AddressValidator.bolt11InvoiceMatcher).hasMatch(e.address.toLowerCase()));
@override
Widget contentWidget(BuildContext context) {
final itemTitleTextStyle = Theme.of(context).textTheme.bodyMedium!.copyWith(
@@ -113,7 +120,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
applyAddressFormatting: false,
copyButton: true,
copyButton: true,
)),
),
if (explanation != null)
@@ -127,7 +134,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
),
StandardTile(
itemTitle: amount,
itemValue: '$amountValue ${currency.title}',
itemValue: '$amountValue ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}',
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitle: fiatAmountValue,
itemSubTitleTextStyle: itemSubTitleTextStyle,
@@ -145,48 +152,49 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
const SizedBox(height: 8),
Column(
children: [
ListView.separated(
padding: const EdgeInsets.only(top: 0),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: outputs.length,
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemBuilder: (context, index) {
final bool isBatchSending = outputs.length > 1;
final item = outputs[index];
final contactName = item.parsedAddress.name;
final isCakePayName = contactName == 'Cake Pay';
final batchContactTitle =
'${index + 1}/${outputs.length} - ${contactName.isEmpty ? 'Address' : contactName}';
final _address = item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = item.cryptoAmount.replaceAll(',', '.') + ' ${currency.title}';
return isBatchSending || (contactName.isNotEmpty && !isCakePayName)
? ExpansionAddressTile(
contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact,
name: isBatchSending ? batchContactTitle : contactName,
address: _address,
amount: _amount,
walletType: walletType,
isBatchSending: isBatchSending,
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
)
: AddressTile(
itemTitle: isCakePayName
? item.parsedAddress.profileName
: S.of(context).address,
imagePath: isCakePayName ? item.parsedAddress.profileImageUrl : null,
itemTitleTextStyle: itemTitleTextStyle,
walletType: walletType,
amount: isCakePayName ? item.fiatAmount : _amount,
address: _address,
itemSubTitle: isCakePayName ? quantity : null,
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
);
},
),
if (showAddress)
ListView.separated(
padding: const EdgeInsets.only(top: 0),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: outputs.length,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, index) {
final isBatchSending = outputs.length > 1;
final item = outputs[index];
final contactName = item.parsedAddress.name;
final isCakePayName = contactName == 'Cake Pay';
final batchContactTitle =
'${index + 1}/${outputs.length} - ${contactName.isEmpty ? 'Address' : contactName}';
final _address = item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = '${item.cryptoAmount.replaceAll(',', '.')} ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}';
return isBatchSending || (contactName.isNotEmpty && !isCakePayName)
? ExpansionAddressTile(
contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact,
name: isBatchSending ? batchContactTitle : contactName,
address: _address,
amount: _amount,
walletType: walletType,
isBatchSending: isBatchSending,
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
)
: AddressTile(
itemTitle: isCakePayName
? item.parsedAddress.profileName
: S.of(context).address,
imagePath: isCakePayName ? item.parsedAddress.profileImageUrl : null,
itemTitleTextStyle: itemTitleTextStyle,
walletType: walletType,
amount: isCakePayName ? item.fiatAmount : _amount,
address: _address,
itemSubTitle: isCakePayName ? quantity : null,
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
);
},
),
if (change != null)
Padding(
padding: const EdgeInsets.only(top: 8),
@@ -194,7 +202,8 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
contactType: 'Change',
name: S.of(context).send_change_to_you,
address: change!.address,
amount: change!.amount + ' ${currency.title}',
amount:
'${amountParsingProxy?.getDisplayCryptoString(change!.amount.toInt(), currency)} ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}',
isBatchSending: true,
walletType: walletType,
itemTitleTextStyle: itemTitleTextStyle,
@@ -1,8 +1,8 @@
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/generate_name.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -153,10 +153,10 @@ class _PaymentConfirmationContent extends StatelessWidget {
bottom: 0,
right: 0,
child: Image.asset(
getCryptoCurrencyForWalletListItem(
getCryptoCurrencyIconForWalletListItem(
paymentViewModel.detectedWalletType!,
chainId: paymentViewModel.detectedChainId,
).iconPath!,
),
width: 32,
height: 32,
errorBuilder: (context, error, stackTrace) => Icon(
@@ -192,10 +192,10 @@ class _PaymentConfirmationContent extends StatelessWidget {
),
] else ...[
Image.asset(
getCryptoCurrencyForWalletListItem(
getCryptoCurrencyIconForWalletListItem(
paymentViewModel.detectedWalletType!,
chainId: paymentViewModel.detectedChainId,
).iconPath!,
),
width: 118,
height: 118,
),
@@ -172,10 +172,10 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
bottom: -4,
right: -4,
child: CakeImageWidget(
imageUrl: getCryptoCurrencyForWalletListItem(
imageUrl: getCryptoCurrencyIconForWalletListItem(
widget.paymentFlowResult.walletType!,
chainId: widget.paymentFlowResult.chainId,
).iconPath!,
),
width: 16,
height: 16,
),
@@ -212,6 +212,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
return AmountValidator(
isAutovalidate: true,
currency: widget.exchangeViewModel.receiveCurrency,
amountParsingProxy: widget.exchangeViewModel.amountParsingProxy,
minValue: widget.exchangeViewModel.limits.min.toString(),
maxValue: widget.exchangeViewModel.limits.max.toString(),
).call(value);
@@ -286,7 +287,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
SwapConfirmationTextfield(
key: ValueKey('swap_confirmation_bottomsheet_address_textfield_key'),
isAddress: true,
walletType: cryptoCurrencyToWalletType(widget.exchangeViewModel.receiveCurrency),
walletType: cryptoCurrencyOrTokenToWalletType(widget.exchangeViewModel.receiveCurrency),
hintText: 'Destination Address',
focusNode: _addressFocus,
controller: _addressController,
@@ -439,7 +440,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
exchangeViewModel.receiveAddress = _addressController.text;
exchangeViewModel.depositAddress = exchangeViewModel.wallet.walletAddresses.addressForExchange;
exchangeViewModel.receiveAmount = _amountController.text;
exchangeViewModel.setCanonicalReceiveAmount(_amountController.text);
_amountFiatController.text = exchangeViewModel.receiveAmountFiatFormatted;
exchangeViewModel.isReceiveAmountEntered = true;
exchangeViewModel.isFixedRateMode = true;
@@ -112,6 +112,9 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
buttonAction: () {
_showingFailureDialog = false;
Navigator.of(popupContext).pop();
if(mounted) {
Navigator.of(context, rootNavigator: true).pop();
}
},
);
},
@@ -172,7 +175,7 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
onSingleActionButtonPressed: () {
Navigator.of(bottomSheetContext).pop();
if (mounted) {
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
;
},
@@ -348,7 +351,7 @@ class _SwapDetailsContent extends StatelessWidget {
const SizedBox(height: 4),
AddressFormatter.buildSegmentedAddress(
address: trade.payoutAddress ?? '',
walletType: trade.to != null ? cryptoCurrencyToWalletType(trade.to!) : null,
walletType: trade.to != null ? cryptoCurrencyOrTokenToWalletType(trade.to!) : null,
evenTextStyle: Theme.of(context)
.textTheme
.bodyMedium!
@@ -97,9 +97,9 @@ class _WalletSwitcherContent extends StatelessWidget {
child: Row(
children: [
Image.asset(
getCryptoCurrencyForWalletListItem(
getCryptoCurrencyIconForWalletListItem(
wallet.type,
).iconPath!,
),
width: 32,
height: 32,
),
+6 -2
View File
@@ -12,6 +12,7 @@ class CakeImageWidget extends StatelessWidget {
this.errorWidget,
this.color,
this.borderRadius = 24.0,
this.colorFilter,
});
final String? imageUrl;
@@ -22,6 +23,7 @@ class CakeImageWidget extends StatelessWidget {
final Widget? errorWidget;
final Color? color;
final double borderRadius;
final ColorFilter? colorFilter;
@override
Widget build(BuildContext context) {
@@ -31,6 +33,8 @@ class CakeImageWidget extends StatelessWidget {
final isSvg = imageUrl!.toLowerCase().endsWith('.svg');
final isAsset = imageUrl!.startsWith('assets/');
final effectiveColorFilter = colorFilter ??
(color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null);
Widget imageWidget;
if (isAsset) {
@@ -39,7 +43,7 @@ class CakeImageWidget extends StatelessWidget {
imageUrl!,
height: height,
width: width,
color: color,
colorFilter: effectiveColorFilter,
fit: fit ?? BoxFit.contain,
)
: Image.asset(
@@ -55,7 +59,7 @@ class CakeImageWidget extends StatelessWidget {
imageUrl!,
height: height,
width: width,
color: color,
colorFilter: effectiveColorFilter,
fit: fit ?? BoxFit.contain,
placeholderBuilder: (_) {
return loadingWidget ?? const Center(child: CircularProgressIndicator());

Some files were not shown because too many files have changed in this diff Show More